Skip to content

Commit

Permalink
untyped APIs for components and resources (bevyengine#4447)
Browse files Browse the repository at this point in the history
# Objective

Even if bevy itself does not provide any builtin scripting or modding APIs, it should have the foundations for building them yourself.
For that it should be enough to have APIs that are not tied to the actual rust types with generics, but rather accept `ComponentId`s and `bevy_ptr` ptrs.

## Solution

Add the following APIs to bevy
```rust
fn EntityRef::get_by_id(ComponentId) -> Option<Ptr<'w>>;
fn EntityMut::get_by_id(ComponentId) -> Option<Ptr<'_>>;
fn EntityMut::get_mut_by_id(ComponentId) -> Option<MutUntyped<'_>>;

fn World::get_resource_by_id(ComponentId) -> Option<Ptr<'_>>;
fn World::get_resource_mut_by_id(ComponentId) -> Option<MutUntyped<'_>>;

// Safety: `value` must point to a valid value of the component
unsafe fn World::insert_resource_by_id(ComponentId, value: OwningPtr);

fn ComponentDescriptor::new_with_layout(..) -> Self;
fn World::init_component_with_descriptor(ComponentDescriptor) -> ComponentId;
```

~~This PR would definitely benefit from bevyengine#3001 (lifetime'd pointers) to make sure that the lifetimes of the pointers are valid and the my-move pointer in `insert_resource_by_id` could be an `OwningPtr`, but that can be adapter later if/when bevyengine#3001 is merged.~~

### Not in this PR
- inserting components on entities (this is very tied to types with bundles and the `BundleInserter`)
- an untyped version of a query (needs good API design, has a large implementation complexity, can be done in a third-party crate)

Co-authored-by: Jakob Hellermann <hellermann@sipgate.de>
  • Loading branch information
2 people authored and ItsDoot committed Feb 1, 2023
1 parent 2d74971 commit bd3269a
Show file tree
Hide file tree
Showing 5 changed files with 535 additions and 22 deletions.
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> {
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> {
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(
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,
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,
..
} = 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(
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 {
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
/// 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
/// 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() {
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

0 comments on commit bd3269a

Please sign in to comment.