Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Use lifetimed, type erased pointers in bevy_ecs #3001

Closed
Closed
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
feea9e0
Codedump!
TheRawMeatball Oct 19, 2021
40b1366
Ptrify everything
TheRawMeatball Oct 23, 2021
33d1844
appease clippy
TheRawMeatball Oct 23, 2021
6c887ad
Move FetchInit to State
TheRawMeatball Oct 23, 2021
3d936f4
Remove ThinSlicePtr
TheRawMeatball Oct 23, 2021
feb49a3
fix pr
TheRawMeatball Jan 10, 2022
9d1cfe8
fix the pr, properly
TheRawMeatball Jan 10, 2022
ea83de9
moar fixing
TheRawMeatball Jan 10, 2022
0181c36
fix mistake
TheRawMeatball Jan 10, 2022
68b0903
docs
TheRawMeatball Jan 10, 2022
ed70596
hopefully final fix
TheRawMeatball Jan 10, 2022
632c130
Merge remote-tracking branch 'upstream/main' into smallish-ptrification
TheRawMeatball Apr 12, 2022
9b71e0b
fix missing impls
TheRawMeatball Apr 12, 2022
c954cf7
change test expectations
TheRawMeatball Apr 12, 2022
7034c14
silence warning
TheRawMeatball Apr 12, 2022
d13ad78
touch up iter_combinations using newer api s
TheRawMeatball Apr 12, 2022
f3a1a89
partial derive WorldQuery fixes
TheRawMeatball Apr 14, 2022
0ea41a7
Make QueryCombinationIter not generic over fetches so it can use Worl…
TheRawMeatball Apr 14, 2022
3e96ad9
rip out 's
TheRawMeatball Apr 15, 2022
379c614
Update `derive(WorldQuery)` (#40)
BoxyUwU Apr 16, 2022
77a6e15
Merge remote-tracking branch 'upstream/main' into smallish-ptrification
TheRawMeatball Apr 16, 2022
c8801b2
Address review
TheRawMeatball Apr 16, 2022
163dbf4
make test use less ridiculous numbers
TheRawMeatball Apr 16, 2022
36ec230
shrink more tests
TheRawMeatball Apr 16, 2022
9a844f5
revert array map
TheRawMeatball Apr 16, 2022
87dd80f
moar fixing
TheRawMeatball Apr 16, 2022
0908c90
revert changes to replace_unchecked
TheRawMeatball Apr 16, 2022
344bbef
fix caught ub
TheRawMeatball Apr 16, 2022
d72bd23
fix silly mistake
TheRawMeatball Apr 16, 2022
148d598
denoise error
TheRawMeatball Apr 16, 2022
ef248cd
fix incorrect deref
TheRawMeatball Apr 16, 2022
7adc7dd
rip out debug code
TheRawMeatball Apr 16, 2022
75e1fa6
Ptrification changes v2 (#41)
BoxyUwU Apr 17, 2022
c03f937
Use UnsafeCellDeref more
TheRawMeatball Apr 17, 2022
cdb442a
Adress some FIXME s
TheRawMeatball Apr 17, 2022
d78c5aa
fix redundant clone
TheRawMeatball Apr 17, 2022
e7258ea
fix mistake
TheRawMeatball Apr 17, 2022
828e0c6
adress CI errors
TheRawMeatball Apr 17, 2022
40c5ef2
fix docs
TheRawMeatball Apr 17, 2022
6d21f3a
Ptrification fetch filter review (#43)
BoxyUwU Apr 18, 2022
134e4ae
Apply suggestions from code review
TheRawMeatball Apr 19, 2022
b76d47a
ptrfication reviews round... 4(?) (#44)
BoxyUwU Apr 19, 2022
1a9d2b4
Apply suggestions from code review
TheRawMeatball Apr 19, 2022
fa466a0
Merge remote-tracking branch 'upstream/main' into smallish-ptrification
TheRawMeatball Apr 19, 2022
7df4761
Merge branch 'smallish-ptrification' of https://github.com/TheRawMeat…
TheRawMeatball Apr 19, 2022
e857b85
use unwrap_unchecked for sparse fetches
TheRawMeatball Apr 19, 2022
26d7c29
Add and use ThinSlicePtr + inlined OwningPtr::make
TheRawMeatball Apr 19, 2022
fcd4f9a
inline all the things
TheRawMeatball Apr 19, 2022
96c7a92
Fix CI (#45)
BoxyUwU Apr 19, 2022
47d8fa6
Apply suggestions from code review
TheRawMeatball Apr 19, 2022
5bedc37
Add safety docs
TheRawMeatball Apr 19, 2022
53518a4
Merge branch 'smallish-ptrification' of https://github.com/TheRawMeat…
TheRawMeatball Apr 19, 2022
5eec29d
even more docs
TheRawMeatball Apr 19, 2022
156f6bf
simplify doc
TheRawMeatball Apr 19, 2022
691874d
Fix docs
TheRawMeatball Apr 20, 2022
3b7baec
Remove #[repr(transparent)]
TheRawMeatball Apr 20, 2022
2704a34
Merge branch 'main' into smallish-ptrification
TheRawMeatball Apr 22, 2022
57fbb7b
boxy docs
BoxyUwU Apr 22, 2022
0ea1718
Merge branch 'main' into smallish-ptrification
TheRawMeatball Apr 23, 2022
c72047d
Remove lifetime and supertrait on `ReadOnlyFetch` (#47)
BoxyUwU Apr 25, 2022
4b0cf4f
Fix type inference regressions (#48)
BoxyUwU Apr 27, 2022
47024d0
Merge remote-tracking branch 'upstream/main' into smallish-ptrification
TheRawMeatball Apr 27, 2022
e3cc656
couple extra fixes
TheRawMeatball Apr 27, 2022
34167f2
cart hates match bool (#49)
BoxyUwU Apr 27, 2022
18add0c
fun is banned
cart Apr 27, 2022
8bc292e
Remove unnecessary instances of `for<'x>`
cart Apr 27, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
412 changes: 161 additions & 251 deletions crates/bevy_ecs/macros/src/fetch.rs

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions crates/bevy_ecs/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,18 +124,17 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
self.#field.get_components(&mut func);
});
field_from_components.push(quote! {
#field: <#field_type as #ecs_path::bundle::Bundle>::from_components(&mut func),
#field: <#field_type as #ecs_path::bundle::Bundle>::from_components(ctx, &mut func),
});
} else {
field_component_ids.push(quote! {
component_ids.push(components.init_component::<#field_type>(storages));
});
field_get_components.push(quote! {
func((&mut self.#field as *mut #field_type).cast::<u8>());
std::mem::forget(self.#field);
#ecs_path::ptr::OwningPtr::make(self.#field, &mut func);
});
field_from_components.push(quote! {
#field: func().cast::<#field_type>().read(),
#field: func(ctx).inner().as_ptr().cast::<#field_type>().read(),
});
}
}
Expand All @@ -157,14 +156,17 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
}

#[allow(unused_variables, unused_mut, non_snake_case)]
unsafe fn from_components(mut func: impl FnMut() -> *mut u8) -> Self {
unsafe fn from_components<T, F>(ctx: &mut T, mut func: F) -> Self
TheRawMeatball marked this conversation as resolved.
Show resolved Hide resolved
where
F: FnMut(&mut T) -> #ecs_path::ptr::OwningPtr<'_>
{
Self {
#(#field_from_components)*
}
}

#[allow(unused_variables, unused_mut, forget_copy, forget_ref)]
fn get_components(mut self, mut func: impl FnMut(*mut u8)) {
fn get_components(self, mut func: impl FnMut(#ecs_path::ptr::OwningPtr<'_>)) {
#(#field_get_components)*
}
}
Expand Down
20 changes: 12 additions & 8 deletions crates/bevy_ecs/src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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;
Expand Down Expand Up @@ -85,14 +86,15 @@ pub unsafe trait Bundle: Send + Sync + 'static {
/// # Safety
/// Caller must return data for each component in the bundle, in the order of this bundle's
/// [`Component`]s
unsafe fn from_components(func: impl FnMut() -> *mut u8) -> Self
unsafe fn from_components<T, F>(ctx: &mut T, func: F) -> Self
where
F: FnMut(&mut T) -> OwningPtr<'_>,
Self: Sized;

/// Calls `func` on each value, in the order of this bundle's [`Component`]s. This will
/// [`std::mem::forget`] the bundle fields, so callers are responsible for dropping the fields
/// if that is desirable.
fn get_components(self, func: impl FnMut(*mut u8));
fn get_components(self, func: impl FnMut(OwningPtr<'_>));
}

macro_rules! tuple_impl {
Expand All @@ -105,21 +107,23 @@ macro_rules! tuple_impl {

#[allow(unused_variables, unused_mut)]
#[allow(clippy::unused_unit)]
unsafe fn from_components(mut func: impl FnMut() -> *mut u8) -> Self {
unsafe fn from_components<T, F>(ctx: &mut T, mut func: F) -> Self
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that the bundle can only live as long as ctx? If we destroy the world, will this cause UB?

Copy link
Member

@alice-i-cecile alice-i-cecile Nov 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just give the Bundle trait a 'world lifetime?

Edit: I don't think so, because we want tuple bundles.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect ctx to exclusively borrow the world?

How would we destroy the world in that case?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it doesn't mean bundle can only live as long as ctx and Bundle doesnt need a 'world lifetime. This function is used when removing a bundle from world so we are taking data out of ctx hence the OwningPtr<'_> and moving it into Self to construct the bundle. The only lifetimes involved are for the temporary borrow on world while we are removing the components to construct our bundle from.

where
F: FnMut(&mut T) -> OwningPtr<'_>
{
#[allow(non_snake_case)]
let ($(mut $name,)*) = (
$(func().cast::<$name>(),)*
$(func(ctx).inner().cast::<$name>(),)*
);
($($name.read(),)*)
($($name.as_ptr().read(),)*)
}

#[allow(unused_variables, unused_mut)]
fn get_components(self, mut func: impl FnMut(*mut u8)) {
fn get_components(self, mut func: impl FnMut(OwningPtr<'_>)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This lifetime is unbounded. I suspect we need an HRTB to properly constrain this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have expected this an anonymous lifetime within the function, i.e. the only assumption the function can make is that it outlives the call.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the same as impl for<'a> FnMut(OwningPtr<'a>) its not unbounded. If you look at the error message in this playground link https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1b3f01da9fb82ca83e808a056801a79e you can see it saying it expects the type for<'a> FnOnce(&'a u8)

#[allow(non_snake_case)]
let ($(mut $name,)*) = self;
$(
func((&mut $name as *mut $name).cast::<u8>());
std::mem::forget($name);
OwningPtr::make($name, &mut func);
)*
}
}
Expand Down
25 changes: 19 additions & 6 deletions crates/bevy_ecs/src/component.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Types for declaring and storing [`Component`]s.

use crate::{
ptr::OwningPtr,
storage::{SparseSetIndex, Storages},
system::Resource,
};
Expand Down Expand Up @@ -117,7 +118,7 @@ impl ComponentInfo {
}

#[inline]
pub fn drop(&self) -> unsafe fn(*mut u8) {
pub fn drop(&self) -> unsafe fn(OwningPtr<'_>) {
self.descriptor.drop
}

Expand Down Expand Up @@ -162,7 +163,6 @@ impl SparseSetIndex for ComponentId {
}
}

#[derive(Debug)]
pub struct ComponentDescriptor {
name: String,
// SAFETY: This must remain private. It must match the statically known StorageType of the
Expand All @@ -173,13 +173,26 @@ pub struct ComponentDescriptor {
is_send_and_sync: bool,
type_id: Option<TypeId>,
layout: Layout,
drop: unsafe fn(*mut u8),
drop: for<'a> unsafe fn(OwningPtr<'a>),
}

// We need to ignore the `drop` field in our `Debug` impl
impl std::fmt::Debug for ComponentDescriptor {
TheRawMeatball marked this conversation as resolved.
Show resolved Hide resolved
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: *mut u8) {
x.cast::<T>().drop_in_place();
unsafe fn drop_ptr<T>(x: OwningPtr<'_>) {
x.inner().cast::<T>().as_ptr().drop_in_place()
}

pub fn new<T: Component>() -> Self {
Expand Down Expand Up @@ -338,7 +351,7 @@ impl Components {
}
}

#[derive(Clone, Debug)]
#[derive(Copy, Clone, Debug)]
pub struct ComponentTicks {
pub(crate) added: u32,
pub(crate) changed: u32,
Expand Down
4 changes: 3 additions & 1 deletion crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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;
Expand Down Expand Up @@ -46,6 +47,7 @@ pub use bevy_ecs_macros::all_tuples;
#[cfg(test)]
mod tests {
use crate as bevy_ecs;
use crate::query::QueryFetch;
use crate::{
bundle::Bundle,
component::{Component, ComponentId},
Expand Down Expand Up @@ -901,7 +903,7 @@ mod tests {

fn get_filtered<F: WorldQuery>(world: &mut World) -> Vec<Entity>
where
F::Fetch: FilterFetch,
for<'x> QueryFetch<'x, F>: FilterFetch<'x>,
{
world
.query_filtered::<Entity, F>()
Expand Down
149 changes: 149 additions & 0 deletions crates/bevy_ecs/src/ptr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
use std::{cell::UnsafeCell, marker::PhantomData, mem::MaybeUninit, ptr::NonNull};

/// Type-erased borrow of some unknown type chosen when constructing this type.
///
/// This type tries to act "borrow-like" which means that:
/// - It should be considered immutable: its target must not be changed while this pointer is alive.
/// - It must always points to a valid value of whatever the pointee type is.
/// - The lifetime `'a` accurately represents how long the pointer is valid for.
///
/// It may be helpful to think of this type as similar to `&'a dyn Any` but without
/// the metadata and able to point to data that does not correspond to a Rust type.
#[derive(Copy, Clone)]
pub struct Ptr<'a>(NonNull<u8>, PhantomData<&'a u8>);
TheRawMeatball marked this conversation as resolved.
Show resolved Hide resolved

/// Type-erased mutable borrow of some unknown type chosen when constructing this type.
///
/// This type tries to act "borrow-like" which means that:
/// - Pointer is considered exclusive and mutable. It cannot be cloned as this would lead to
/// aliased mutability.
/// - It must always points to a valid value of whatever the pointee type is.
/// - The lifetime `'a` accurately represents how long the pointer is valid for.
///
/// It may be helpful to think of this type as similar to `&'a mut dyn Any` but without
/// the metadata and able to point to data that does not correspond to a Rust type.
pub struct PtrMut<'a>(NonNull<u8>, PhantomData<&'a mut u8>);
TheRawMeatball marked this conversation as resolved.
Show resolved Hide resolved

/// Type-erased Box-like pointer to some unknown type chosen when constructing this type.
/// Conceptually represents ownership of whatever data is being pointed to and so is
/// responsible for calling its `Drop` impl. This pointer is _not_ responsible for freeing
/// the memory pointed to by this pointer as it may be pointing to an element in a `Vec` or
/// to a local in a function etc.
///
/// This type tries to act "borrow-like" like which means that:
/// - Pointer should be considered exclusive and mutable. It cannot be cloned as this would lead
/// to aliased mutability and potentially use after free bugs.
/// - It must always points to a valid value of whatever the pointee type is.
/// - The lifetime `'a` accurately represents how long the pointer is valid for.
///
/// It may be helpful to think of this type as similar to `&'a mut ManuallyDrop<dyn Any>` but
/// without the metadata and able to point to data that does not correspond to a Rust type.
pub struct OwningPtr<'a>(NonNull<u8>, PhantomData<&'a mut u8>);
TheRawMeatball marked this conversation as resolved.
Show resolved Hide resolved

macro_rules! impl_ptr {
($ptr:ident) => {
impl $ptr<'_> {
/// # Safety
/// the offset cannot make the existing ptr null, or take it out of bounds for its allocation.
pub unsafe fn offset(self, count: isize) -> Self {
Self(
NonNull::new_unchecked(self.0.as_ptr().offset(count)),
PhantomData,
)
}

/// # Safety
/// the offset cannot make the existing ptr null, or take it out of bounds for its allocation.
pub unsafe fn add(self, count: usize) -> Self {
Self(
NonNull::new_unchecked(self.0.as_ptr().add(count)),
PhantomData,
)
}

/// # Safety
///
/// The lifetime for the returned item must not exceed the lifetime `inner` is valid for
pub unsafe fn new(inner: NonNull<u8>) -> Self {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems extremely dangerous to use due to inferred lifetimes: this is very easy to mess up and will result in UB lifetime extensions if you do.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This problem is repeated elsewhere, e.g. on OwningPointer::make.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is nothing that can be done about this, its the same problem as dereferencing a raw pointer creating an unbounded lifetime which has to happen at some point anyway to turn it into a reference.

Self(inner, PhantomData)
}

pub fn inner(&self) -> NonNull<u8> {
self.0
}
}
};
}

impl_ptr!(Ptr);
impl<'a> Ptr<'a> {
/// # Safety
///
/// Another [`PtrMut`] for the same [`Ptr`] must not be created until the first is dropped.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be more specific about "values pointed to by Ptr", otherwise it might be interpreted as "for a given Ptr reference", which obviously isn't guaranteed to be unique.

pub unsafe fn assert_unique(self) -> PtrMut<'a> {
PtrMut(self.0, PhantomData)
}

/// # Safety
/// Must point to a valid `T`
pub unsafe fn deref<T>(self) -> &'a T {
&*self.0.as_ptr().cast()
}
}
impl_ptr!(PtrMut);
impl<'a> PtrMut<'a> {
/// Transforms this [`PtrMut`] into an [`OwningPtr`]
///
/// # Safety
TheRawMeatball marked this conversation as resolved.
Show resolved Hide resolved
/// Must have right to drop or move out of [`PtrMut`], and current [`PtrMut`] should not be accessed again unless it's written to again.
TheRawMeatball marked this conversation as resolved.
Show resolved Hide resolved
pub unsafe fn promote(self) -> OwningPtr<'a> {
OwningPtr(self.0, PhantomData)
}

/// Transforms this [`PtrMut<T>`] into a `&mut T` with the same lifetime
///
/// # Safety
TheRawMeatball marked this conversation as resolved.
Show resolved Hide resolved
/// Must point to a valid `T`
pub unsafe fn deref_mut<T>(self) -> &'a mut T {
&mut *self.inner().as_ptr().cast()
}
}
impl_ptr!(OwningPtr);
impl<'a> OwningPtr<'a> {
pub fn make<T, F: FnOnce(OwningPtr<'_>) -> R, R>(val: T, f: F) -> R {
TheRawMeatball marked this conversation as resolved.
Show resolved Hide resolved
let mut temp = MaybeUninit::new(val);
let ptr = unsafe { NonNull::new_unchecked(temp.as_mut_ptr().cast::<u8>()) };
f(Self(ptr, PhantomData))
}

//// Consumes the [`OwningPtr`] to obtain ownership of the underlying data of type `T`.
///
/// # Safety
TheRawMeatball marked this conversation as resolved.
Show resolved Hide resolved
/// Must point to a valid `T`.
pub unsafe fn read<T>(self) -> T {
self.inner().as_ptr().cast::<T>().read()
}
}

pub(crate) trait UnsafeCellDeref<'a, T> {
unsafe fn deref_mut(self) -> &'a mut T;
unsafe fn deref(self) -> &'a T;
unsafe fn read(self) -> T
where
T: Copy;
}
impl<'a, T> UnsafeCellDeref<'a, T> for &'a UnsafeCell<T> {
unsafe fn deref_mut(self) -> &'a mut T {
&mut *self.get()
}
unsafe fn deref(self) -> &'a T {
&*self.get()
}

unsafe fn read(self) -> T
where
T: Copy,
{
self.get().read()
}
}
Loading