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] - untyped APIs for components and resources #4447

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
d0d8d37
bevy_ecs: untyped API to access components
jakobhellermann Jan 27, 2022
ae98499
bevy_ecs: untyped API to access resources
jakobhellermann Apr 9, 2022
d13bbe5
validate nonsend correctness for dynamic resource APIs
jakobhellermann Apr 9, 2022
497685c
bevy_ecs: add insert_resource_by_id
jakobhellermann Apr 9, 2022
9bcbcae
bevy_ecs: untyped API to init components
jakobhellermann Apr 9, 2022
612fd92
bevy_ecs: add Components::iter
jakobhellermann Apr 7, 2022
64f81af
remove TypeId and is_send_and_sync from ComponentDescriptor::new_with…
jakobhellermann Apr 10, 2022
3201c1a
add docs
jakobhellermann Apr 10, 2022
3493688
add tests
jakobhellermann Apr 10, 2022
0dc2f4c
add #[inline]
jakobhellermann Apr 10, 2022
6efc899
two more safety comments
jakobhellermann Apr 10, 2022
1698a7b
fix broken link
jakobhellermann Apr 10, 2022
1db081f
move methods in separate `impl` block so rustdoc deprioritizes them,
jakobhellermann Apr 12, 2022
77c8235
move warning notes to second paragraph
jakobhellermann Apr 19, 2022
720e60b
move resource_by_id methods on world to separate impl block
jakobhellermann Apr 19, 2022
f0280d7
add test for ComponentDescriptor::new_with_layout
jakobhellermann Apr 19, 2022
ce2b0d0
add World::remove_resource_by_id
jakobhellermann Apr 19, 2022
a3f2c6d
add some #[inline] attributes
jakobhellermann Apr 19, 2022
7b6045a
Merge branch 'main' into bevy-ecs-dynamic
jakobhellermann May 1, 2022
974075d
use Ptr types
jakobhellermann May 1, 2022
06de78c
use UnsafeCellDeref::deref_mut
jakobhellermann May 2, 2022
5fa60ef
trigger CI
jakobhellermann May 3, 2022
b2a3fb8
Merge branch 'main' into bevy-ecs-dynamic
jakobhellermann May 3, 2022
1253338
add missing function
jakobhellermann May 3, 2022
b9263ae
Merge branch 'main' into bevy-ecs-dynamic
jakobhellermann May 7, 2022
5bd27d7
Merge branch 'main' into bevy-ecs-dynamic
jakobhellermann May 9, 2022
396d437
add world.get_by_id and world.get_mut_by_id
jakobhellermann May 9, 2022
faac9f4
validate ComponentIds before passing them to unsafe functions expecti…
jakobhellermann May 9, 2022
6051bc7
clean up docs
jakobhellermann May 13, 2022
756d009
cargo fmt
jakobhellermann May 17, 2022
cc784d2
Update crates/bevy_ecs/src/change_detection.rs
jakobhellermann May 17, 2022
b22ec5a
change EntityRef::get_by_id to return 'w ptr, like its typed variant
jakobhellermann May 17, 2022
b02170a
Merge branch 'main' into bevy-ecs-dynamic
jakobhellermann May 17, 2022
041688e
update to optional drop pointer in ComponentDescriptor
jakobhellermann May 17, 2022
3965a1b
Merge branch 'main' into bevy-ecs-dynamic
jakobhellermann May 24, 2022
9547a9b
fix new_with_layout constructor in test
jakobhellermann May 24, 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
56 changes: 55 additions & 1 deletion crates/bevy_ecs/src/change_detection.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Types that detect when their internal data mutate.

use crate::{component::ComponentTicks, system::Resource};
use crate::{component::ComponentTicks, ptr::PtrMut, system::Resource};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
use std::ops::{Deref, DerefMut};
Expand Down Expand Up @@ -228,6 +228,60 @@ change_detection_impl!(ReflectMut<'a>, dyn Reflect,);
#[cfg(feature = "bevy_reflect")]
impl_into_inner!(ReflectMut<'a>, dyn Reflect,);

/// Unique mutable borrow of resources or an entity's component.
///
/// Similar to [`Mut`], but not generic over the component type, instead
/// exposing the raw pointer as a `*mut ()`.
///
/// Usually you don't need to use this and can instead use the APIs returning a
/// [`Mut`], but in situations where the types are not known at compile time
/// or are defined outside of rust this can be used.
pub struct MutUntyped<'a> {
jakobhellermann marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) value: PtrMut<'a>,
pub(crate) ticks: Ticks<'a>,
}

impl<'a> MutUntyped<'a> {
/// Returns the pointer to the value, without marking it as changed.
///
/// In order to mark the value as changed, you need to call [`set_changed`](DetectChanges::set_changed) manually.
pub fn into_inner(self) -> PtrMut<'a> {
Copy link
Contributor

Choose a reason for hiding this comment

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

You can't call this method before set_changed due to it taking self by value.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, yeah.

A fn into_inner(&mut self) -> &mut PtrMut<'a> wouldn't be very useful since methods like PtrMut::deref_mut take self by value.
fn into_inner(&mut self) -> PtrMut<'a> would be bad since you could create aliasing PtrMuts with it.
fn into_inner<'s>(&'s mut self) -> PtrMut<'s>) should be fine I think.

Alternatively we could let into_inner return (PtrMut<'a>, Ticks<'a>) but we'd have to make Ticks public.

Copy link
Member

Choose a reason for hiding this comment

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

You can call set_changed first at least, so I think I'm okay with this design.

self.value
}
}

impl DetectChanges for MutUntyped<'_> {
fn is_added(&self) -> bool {
self.ticks
.component_ticks
.is_added(self.ticks.last_change_tick, self.ticks.change_tick)
}

fn is_changed(&self) -> bool {
self.ticks
.component_ticks
.is_changed(self.ticks.last_change_tick, self.ticks.change_tick)
}

fn set_changed(&mut self) {
self.ticks
.component_ticks
.set_changed(self.ticks.change_tick);
}

fn last_changed(&self) -> u32 {
self.ticks.last_change_tick
}
}

impl std::fmt::Debug for MutUntyped<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("MutUntyped")
.field(&self.value.as_ptr())
.finish()
}
}

#[cfg(test)]
mod tests {
use crate::{
Expand Down
68 changes: 58 additions & 10 deletions crates/bevy_ecs/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ impl ComponentDescriptor {
x.drop_as::<T>()
}

/// Create a new `ComponentDescriptor` for the type `T`.
pub fn new<T: Component>() -> Self {
Self {
name: std::any::type_name::<T>().to_string(),
Expand All @@ -209,6 +210,27 @@ impl ComponentDescriptor {
}
}

/// Create a new `ComponentDescriptor`.
///
/// # Safety
/// - the `drop` fn must be usable on a pointer with a value of the layout `layout`
/// - the component type must be safe to access from any thread (Send + Sync in rust terms)
pub unsafe fn new_with_layout(
jakobhellermann marked this conversation as resolved.
Show resolved Hide resolved
name: String,
storage_type: StorageType,
layout: Layout,
drop: Option<for<'a> unsafe fn(OwningPtr<'a>)>,
) -> Self {
Self {
name,
storage_type,
is_send_and_sync: true,
jakobhellermann marked this conversation as resolved.
Show resolved Hide resolved
type_id: None,
layout,
drop,
}
}

/// Create a new `ComponentDescriptor` for a resource.
///
/// The [`StorageType`] for resources is always [`TableStorage`].
Expand Down Expand Up @@ -263,20 +285,42 @@ 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

let Components {
indices,
components,
..
jakobhellermann marked this conversation as resolved.
Show resolved Hide resolved
} = self;
let index = indices.entry(type_id).or_insert_with(|| {
Components::init_component_inner(components, storages, ComponentDescriptor::new::<T>())
});
ComponentId(*index)
}

pub fn init_component_with_descriptor(
&mut self,
storages: &mut Storages,
descriptor: ComponentDescriptor,
) -> ComponentId {
let index = Components::init_component_inner(&mut self.components, storages, descriptor);
ComponentId(index)
}

#[inline]
fn init_component_inner(
jakobhellermann marked this conversation as resolved.
Show resolved Hide resolved
components: &mut Vec<ComponentInfo>,
storages: &mut Storages,
descriptor: ComponentDescriptor,
) -> usize {
let index = components.len();
let info = ComponentInfo::new(ComponentId(index), descriptor);
if info.descriptor.storage_type == StorageType::SparseSet {
storages.sparse_sets.get_or_insert(&info);
}
components.push(info);
index
}

#[inline]
pub fn len(&self) -> usize {
self.components.len()
Expand Down Expand Up @@ -352,6 +396,10 @@ impl Components {

ComponentId(*index)
}

pub fn iter(&self) -> impl Iterator<Item = &ComponentInfo> + '_ {
self.components.iter()
}
}

/// Records when a component was added and when it was last mutably dereferenced (or added).
Expand Down
5 changes: 5 additions & 0 deletions crates/bevy_ecs/src/storage/blob_vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ impl BlobVec {
self.capacity
}

#[inline]
pub fn layout(&self) -> Layout {
jakobhellermann marked this conversation as resolved.
Show resolved Hide resolved
self.item_layout
}

pub fn reserve_exact(&mut self, additional: usize) {
let available_space = self.capacity - self.len;
if available_space < additional {
Expand Down
153 changes: 147 additions & 6 deletions crates/bevy_ecs/src/world/entity_ref.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
archetype::{Archetype, ArchetypeId, Archetypes},
bundle::{Bundle, BundleInfo},
change_detection::Ticks,
change_detection::{MutUntyped, Ticks},
component::{Component, ComponentId, ComponentTicks, Components, StorageType},
entity::{Entities, Entity, EntityLocation},
storage::{SparseSet, Storages},
Expand Down Expand Up @@ -103,14 +103,31 @@ impl<'w> EntityRef<'w> {
.map(|(value, ticks)| Mut {
value: value.assert_unique().deref_mut::<T>(),
ticks: Ticks {
component_ticks: &mut *ticks.get(),
component_ticks: ticks.deref_mut(),
last_change_tick,
change_tick,
},
})
}
}

impl<'w> EntityRef<'w> {
/// Gets the component of the given [`ComponentId`] from the entity.
///
/// **You should prefer to use the typed API where possible and only
/// use this in cases where the actual component types are not known at
/// compile time.**
///
/// Unlike [`EntityRef::get`], this returns a raw pointer to the component,
/// which is only valid while the `'w` borrow of the lifetime is active.
#[inline]
pub fn get_by_id(&self, component_id: ComponentId) -> Option<Ptr<'w>> {
self.world.components().get_info(component_id)?;
// SAFE: entity_location is valid, component_id is valid as checked by the line above
unsafe { get_component(self.world, component_id, self.entity, self.location) }
}
}

/// A mutable reference to a particular [`Entity`] and all of its components
pub struct EntityMut<'w> {
world: &'w mut World,
Expand Down Expand Up @@ -207,7 +224,7 @@ impl<'w> EntityMut<'w> {
.map(|(value, ticks)| Mut {
value: value.assert_unique().deref_mut::<T>(),
ticks: Ticks {
component_ticks: &mut *ticks.get(),
component_ticks: ticks.deref_mut(),
last_change_tick: self.world.last_change_tick(),
change_tick: self.world.read_change_tick(),
},
Expand Down Expand Up @@ -488,14 +505,47 @@ impl<'w> EntityMut<'w> {
}
}

impl<'w> EntityMut<'w> {
/// Gets the component of the given [`ComponentId`] from the entity.
///
/// **You should prefer to use the typed API [`EntityMut::get`] where possible and only
jakobhellermann marked this conversation as resolved.
Show resolved Hide resolved
/// use this in cases where the actual component types are not known at
/// compile time.**
///
/// Unlike [`EntityMut::get`], this returns a raw pointer to the component,
/// which is only valid while the [`EntityMut`] is alive.
#[inline]
pub fn get_by_id(&self, component_id: ComponentId) -> Option<Ptr<'_>> {
self.world.components().get_info(component_id)?;
// SAFE: entity_location is valid, component_id is valid as checked by the line above
unsafe { get_component(self.world, component_id, self.entity, self.location) }
}

/// Gets a [`MutUntyped`] of the component of the given [`ComponentId`] from the entity.
///
/// **You should prefer to use the typed API [`EntityMut::get_mut`] where possible and only
jakobhellermann marked this conversation as resolved.
Show resolved Hide resolved
/// use this in cases where the actual component types are not known at
/// compile time.**
///
/// Unlike [`EntityMut::get_mut`], this returns a raw pointer to the component,
/// which is only valid while the [`EntityMut`] is alive.
#[inline]
pub fn get_mut_by_id(&mut self, component_id: ComponentId) -> Option<MutUntyped<'_>> {
self.world.components().get_info(component_id)?;
// SAFE: entity_location is valid, component_id is valid as checked by the line above
unsafe { get_mut_by_id(self.world, self.entity, self.location, component_id) }
}
}

// TODO: move to Storages?
/// Get a raw pointer to a particular [`Component`] on a particular [`Entity`] in the provided [`World`].
///
/// # Safety
/// `entity_location` must be within bounds of the given archetype and `entity` must exist inside
/// - `entity_location` must be within bounds of the given archetype and `entity` must exist inside
/// the archetype
/// - `component_id` must be valid
#[inline]
unsafe fn get_component(
pub(crate) unsafe fn get_component(
world: &World,
component_id: ComponentId,
entity: Entity,
Expand Down Expand Up @@ -808,16 +858,41 @@ pub(crate) unsafe fn get_mut<T: Component>(
|(value, ticks)| Mut {
value: value.assert_unique().deref_mut::<T>(),
ticks: Ticks {
component_ticks: &mut *ticks.get(),
component_ticks: ticks.deref_mut(),
last_change_tick,
change_tick,
},
},
)
}

// SAFETY: EntityLocation must be valid, component_id must be valid
#[inline]
pub(crate) unsafe fn get_mut_by_id(
world: &mut World,
entity: Entity,
location: EntityLocation,
component_id: ComponentId,
) -> Option<MutUntyped> {
// SAFE: world access is unique, entity location and component_id required to be valid
get_component_and_ticks(world, component_id, entity, location).map(|(value, ticks)| {
MutUntyped {
value: value.assert_unique(),
ticks: Ticks {
component_ticks: ticks.deref_mut(),
last_change_tick: world.last_change_tick(),
change_tick: world.read_change_tick(),
},
}
})
}

#[cfg(test)]
mod tests {
use crate as bevy_ecs;
use crate::component::ComponentId;
use crate::prelude::*; // for the `#[derive(Component)]`

#[test]
fn sorted_remove() {
let mut a = vec![1, 2, 3, 4, 5, 6, 7];
Expand All @@ -838,4 +913,70 @@ mod tests {

assert_eq!(a, vec![1]);
}

#[derive(Component)]
struct TestComponent(u32);

#[test]
fn entity_ref_get_by_id() {
jakobhellermann marked this conversation as resolved.
Show resolved Hide resolved
let mut world = World::new();
let entity = world.spawn().insert(TestComponent(42)).id();
let component_id = world
.components()
.get_id(std::any::TypeId::of::<TestComponent>())
.unwrap();

let entity = world.entity(entity);
let test_component = entity.get_by_id(component_id).unwrap();
// SAFE: points to a valid `TestComponent`
let test_component = unsafe { test_component.deref::<TestComponent>() };

assert_eq!(test_component.0, 42);
}

#[test]
fn entity_mut_get_by_id() {
let mut world = World::new();
let entity = world.spawn().insert(TestComponent(42)).id();
let component_id = world
.components()
.get_id(std::any::TypeId::of::<TestComponent>())
.unwrap();

let mut entity_mut = world.entity_mut(entity);
let mut test_component = entity_mut.get_mut_by_id(component_id).unwrap();
{
test_component.set_changed();
// SAFE: `test_component` has unique access of the `EntityMut` and is not used afterwards
let test_component =
unsafe { test_component.into_inner().deref_mut::<TestComponent>() };
test_component.0 = 43;
}

let entity = world.entity(entity);
let test_component = entity.get_by_id(component_id).unwrap();
let test_component = unsafe { test_component.deref::<TestComponent>() };

assert_eq!(test_component.0, 43);
}

#[test]
fn entity_ref_get_by_id_invalid_component_id() {
let invalid_component_id = ComponentId::new(usize::MAX);

let mut world = World::new();
let entity = world.spawn().id();
let entity = world.entity(entity);
assert!(entity.get_by_id(invalid_component_id).is_none());
}

#[test]
fn entity_mut_get_by_id_invalid_component_id() {
let invalid_component_id = ComponentId::new(usize::MAX);

let mut world = World::new();
let mut entity = world.spawn();
assert!(entity.get_by_id(invalid_component_id).is_none());
assert!(entity.get_mut_by_id(invalid_component_id).is_none());
}
}
Loading