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 6 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
40 changes: 40 additions & 0 deletions crates/bevy_ecs/src/change_detection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,43 @@ pub struct ReflectMut<'a> {
change_detection_impl!(ReflectMut<'a>, dyn Reflect,);
#[cfg(feature = "bevy_reflect")]
impl_into_inner!(ReflectMut<'a>, dyn Reflect,);

pub struct MutUntyped<'a> {
jakobhellermann marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) value: *mut (),
pub(crate) ticks: Ticks<'a>,
}

impl<'a> MutUntyped<'a> {
/// Returns the pointer to the value, without marking it as changed.
/// The value is only valid for the lifetime `'a`, after which it must not be used anymore.
/// In order to mark the value as change, you need to call [`set_changed`] manually
pub fn ptr(&self) -> *mut () {
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);
}
}

impl std::fmt::Debug for MutUntyped<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("MutDynamic").field(&self.value).finish()
}
}
63 changes: 53 additions & 10 deletions crates/bevy_ecs/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,24 @@ impl ComponentDescriptor {
}
}

pub unsafe fn new_with_layout(
jakobhellermann marked this conversation as resolved.
Show resolved Hide resolved
name: String,
storage_type: StorageType,
is_send_and_sync: bool,
type_id: Option<TypeId>,
layout: Layout,
drop: unsafe fn(*mut u8),
) -> Self {
Self {
name,
storage_type,
is_send_and_sync,
type_id,
layout,
drop,
}
}

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

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 @@ -336,6 +375,10 @@ impl Components {

ComponentId(*index)
}

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

#[derive(Clone, Debug)]
Expand Down
4 changes: 4 additions & 0 deletions crates/bevy_ecs/src/storage/blob_vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ impl BlobVec {
self.capacity
}

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
32 changes: 31 additions & 1 deletion 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 @@ -70,6 +70,13 @@ impl<'w> EntityRef<'w> {
}
}

pub fn get_by_id(&self, component_id: ComponentId) -> Option<*const ()> {
jakobhellermann marked this conversation as resolved.
Show resolved Hide resolved
unsafe {
get_component(self.world, component_id, self.entity, self.location)
.map(|ptr| ptr as *const ())
}
}

/// Gets a mutable reference to the component of type `T` associated with
/// this entity without ensuring there are no other borrows active and without
/// ensuring that the returned reference will stay valid.
Expand Down Expand Up @@ -157,12 +164,35 @@ impl<'w> EntityMut<'w> {
unsafe { self.get_unchecked::<T>() }
}

pub fn get_by_id(&self, component_id: ComponentId) -> Option<*const ()> {
unsafe {
get_component(self.world, component_id, self.entity, self.location)
.map(|ptr| ptr as *const ())
}
}

#[inline]
pub fn get_mut<T: Component>(&mut self) -> Option<Mut<'_, T>> {
// SAFE: world access is unique, and lifetimes enforce correct usage of returned borrow
unsafe { self.get_unchecked_mut::<T>() }
}

pub fn get_mut_by_id(&mut self, component_id: ComponentId) -> Option<MutUntyped> {
// SAFE: world access is unique and entity location is valid
unsafe {
get_component_and_ticks(self.world, component_id, self.entity, self.location).map(
|(value, ticks)| MutUntyped {
value: value.cast(),
ticks: Ticks {
component_ticks: &mut *ticks,
last_change_tick: self.world.last_change_tick(),
change_tick: self.world.read_change_tick(),
},
},
)
}
}

/// Gets an immutable reference to the component of type `T` associated with
/// this entity without ensuring there are no unique borrows active and without
/// ensuring that the returned reference will stay valid.
Expand Down
70 changes: 68 additions & 2 deletions crates/bevy_ecs/src/world/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ pub use world_cell::*;
use crate::{
archetype::{ArchetypeComponentId, ArchetypeComponentInfo, ArchetypeId, Archetypes},
bundle::{Bundle, BundleInserter, BundleSpawner, Bundles},
change_detection::Ticks,
component::{Component, ComponentId, ComponentTicks, Components, StorageType},
change_detection::{MutUntyped, Ticks},
component::{
Component, ComponentDescriptor, ComponentId, ComponentTicks, Components, StorageType,
},
entity::{AllocAtWithoutReplacement, Entities, Entity},
query::{FilterFetch, QueryState, WorldQuery},
storage::{Column, SparseSet, Storages},
Expand Down Expand Up @@ -179,6 +181,14 @@ impl World {
self.components.init_component::<T>(&mut self.storages)
}

pub fn init_component_with_descriptor(
&mut self,
descriptor: ComponentDescriptor,
) -> ComponentId {
self.components
.init_component_with_descriptor(&mut self.storages, descriptor)
}

/// Retrieves an [`EntityRef`] that exposes read-only operations for the given `entity`.
/// This will panic if the `entity` does not exist. Use [`World::get_entity`] if you want
/// to check for entity existence instead of implicitly panic-ing.
Expand Down Expand Up @@ -1095,6 +1105,33 @@ impl World {
Some(&*column.get_data_ptr().as_ptr().cast::<R>())
}

pub fn get_resource_by_id(&self, component_id: ComponentId) -> Option<*const ()> {
let info = self.components.get_info(component_id)?;
if !info.is_send_and_sync() {
self.validate_non_send_access_untyped(info.name());
}

let column = self.get_populated_resource_column(component_id)?;
Some(unsafe { column.get_data_ptr().as_ptr().cast() })
}

pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option<MutUntyped> {
let info = self.components.get_info(component_id)?;
if !info.is_send_and_sync() {
self.validate_non_send_access_untyped(info.name());
}

let column = self.get_populated_resource_column(component_id)?;
let value = unsafe { column.get_data_ptr().as_ptr().cast() };
jakobhellermann marked this conversation as resolved.
Show resolved Hide resolved
let ticks = Ticks {
component_ticks: unsafe { &mut *column.get_ticks_mut_ptr_unchecked(0) },
jakobhellermann marked this conversation as resolved.
Show resolved Hide resolved
last_change_tick: self.last_change_tick(),
change_tick: self.read_change_tick(),
};

Some(MutUntyped { value, ticks })
}

/// # Safety
/// `component_id` must be assigned to a component of type `R`
/// Caller must ensure this doesn't violate Rust mutability rules for the given resource.
Expand Down Expand Up @@ -1155,6 +1192,27 @@ impl World {
}
}

/// # Safety
/// The value referenced by `value` must be valid for the given ComponentId
pub unsafe fn insert_resource_by_id(&mut self, component_id: ComponentId, value: *const ()) {
let change_tick = self.change_tick();
let column = self.initialize_resource_internal(component_id);
if column.is_empty() {
let value = ManuallyDrop::new(value);
// SAFE: column has been allocated above and `value` is valid as per the function's safety requirements
column.push(
value.cast::<u8>() as *mut _,
ComponentTicks::new(change_tick),
);
} else {
// SAFE: column has been allocated above and `value` is valid as per the function's safety requirements
let ptr = column.get_data_unchecked(0).cast::<u8>();
std::ptr::copy_nonoverlapping(value.cast::<u8>(), ptr, column.data.layout().size());

column.get_ticks_unchecked_mut(0).set_changed(change_tick);
}
}

/// # Safety
/// `component_id` must be valid and correspond to a resource component of type `R`
#[inline]
Expand Down Expand Up @@ -1223,6 +1281,14 @@ impl World {
);
}

pub(crate) fn validate_non_send_access_untyped(&self, name: &str) {
assert!(
self.main_thread_validator.is_main_thread(),
"attempted to access NonSend resource {} off of the main thread",
name
);
}

/// Empties queued entities and adds them to the empty [Archetype](crate::archetype::Archetype).
/// This should be called before doing operations that might operate on queued entities,
/// such as inserting a [Component].
Expand Down