Skip to content

Commit bd3269a

Browse files
jakobhellermannItsDoot
authored andcommitted
untyped APIs for components and resources (bevyengine#4447)
# 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 <[email protected]>
1 parent 2d74971 commit bd3269a

File tree

5 files changed

+535
-22
lines changed

5 files changed

+535
-22
lines changed

crates/bevy_ecs/src/change_detection.rs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Types that detect when their internal data mutate.
22
3-
use crate::{component::ComponentTicks, system::Resource};
3+
use crate::{component::ComponentTicks, ptr::PtrMut, system::Resource};
44
#[cfg(feature = "bevy_reflect")]
55
use bevy_reflect::Reflect;
66
use std::ops::{Deref, DerefMut};
@@ -228,6 +228,60 @@ change_detection_impl!(ReflectMut<'a>, dyn Reflect,);
228228
#[cfg(feature = "bevy_reflect")]
229229
impl_into_inner!(ReflectMut<'a>, dyn Reflect,);
230230

231+
/// Unique mutable borrow of resources or an entity's component.
232+
///
233+
/// Similar to [`Mut`], but not generic over the component type, instead
234+
/// exposing the raw pointer as a `*mut ()`.
235+
///
236+
/// Usually you don't need to use this and can instead use the APIs returning a
237+
/// [`Mut`], but in situations where the types are not known at compile time
238+
/// or are defined outside of rust this can be used.
239+
pub struct MutUntyped<'a> {
240+
pub(crate) value: PtrMut<'a>,
241+
pub(crate) ticks: Ticks<'a>,
242+
}
243+
244+
impl<'a> MutUntyped<'a> {
245+
/// Returns the pointer to the value, without marking it as changed.
246+
///
247+
/// In order to mark the value as changed, you need to call [`set_changed`](DetectChanges::set_changed) manually.
248+
pub fn into_inner(self) -> PtrMut<'a> {
249+
self.value
250+
}
251+
}
252+
253+
impl DetectChanges for MutUntyped<'_> {
254+
fn is_added(&self) -> bool {
255+
self.ticks
256+
.component_ticks
257+
.is_added(self.ticks.last_change_tick, self.ticks.change_tick)
258+
}
259+
260+
fn is_changed(&self) -> bool {
261+
self.ticks
262+
.component_ticks
263+
.is_changed(self.ticks.last_change_tick, self.ticks.change_tick)
264+
}
265+
266+
fn set_changed(&mut self) {
267+
self.ticks
268+
.component_ticks
269+
.set_changed(self.ticks.change_tick);
270+
}
271+
272+
fn last_changed(&self) -> u32 {
273+
self.ticks.last_change_tick
274+
}
275+
}
276+
277+
impl std::fmt::Debug for MutUntyped<'_> {
278+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279+
f.debug_tuple("MutUntyped")
280+
.field(&self.value.as_ptr())
281+
.finish()
282+
}
283+
}
284+
231285
#[cfg(test)]
232286
mod tests {
233287
use crate::{

crates/bevy_ecs/src/component.rs

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ impl ComponentDescriptor {
198198
x.drop_as::<T>()
199199
}
200200

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

213+
/// Create a new `ComponentDescriptor`.
214+
///
215+
/// # Safety
216+
/// - the `drop` fn must be usable on a pointer with a value of the layout `layout`
217+
/// - the component type must be safe to access from any thread (Send + Sync in rust terms)
218+
pub unsafe fn new_with_layout(
219+
name: String,
220+
storage_type: StorageType,
221+
layout: Layout,
222+
drop: Option<for<'a> unsafe fn(OwningPtr<'a>)>,
223+
) -> Self {
224+
Self {
225+
name,
226+
storage_type,
227+
is_send_and_sync: true,
228+
type_id: None,
229+
layout,
230+
drop,
231+
}
232+
}
233+
212234
/// Create a new `ComponentDescriptor` for a resource.
213235
///
214236
/// The [`StorageType`] for resources is always [`TableStorage`].
@@ -263,20 +285,42 @@ impl Components {
263285
#[inline]
264286
pub fn init_component<T: Component>(&mut self, storages: &mut Storages) -> ComponentId {
265287
let type_id = TypeId::of::<T>();
266-
let components = &mut self.components;
267-
let index = self.indices.entry(type_id).or_insert_with(|| {
268-
let index = components.len();
269-
let descriptor = ComponentDescriptor::new::<T>();
270-
let info = ComponentInfo::new(ComponentId(index), descriptor);
271-
if T::Storage::STORAGE_TYPE == StorageType::SparseSet {
272-
storages.sparse_sets.get_or_insert(&info);
273-
}
274-
components.push(info);
275-
index
288+
289+
let Components {
290+
indices,
291+
components,
292+
..
293+
} = self;
294+
let index = indices.entry(type_id).or_insert_with(|| {
295+
Components::init_component_inner(components, storages, ComponentDescriptor::new::<T>())
276296
});
277297
ComponentId(*index)
278298
}
279299

300+
pub fn init_component_with_descriptor(
301+
&mut self,
302+
storages: &mut Storages,
303+
descriptor: ComponentDescriptor,
304+
) -> ComponentId {
305+
let index = Components::init_component_inner(&mut self.components, storages, descriptor);
306+
ComponentId(index)
307+
}
308+
309+
#[inline]
310+
fn init_component_inner(
311+
components: &mut Vec<ComponentInfo>,
312+
storages: &mut Storages,
313+
descriptor: ComponentDescriptor,
314+
) -> usize {
315+
let index = components.len();
316+
let info = ComponentInfo::new(ComponentId(index), descriptor);
317+
if info.descriptor.storage_type == StorageType::SparseSet {
318+
storages.sparse_sets.get_or_insert(&info);
319+
}
320+
components.push(info);
321+
index
322+
}
323+
280324
#[inline]
281325
pub fn len(&self) -> usize {
282326
self.components.len()
@@ -352,6 +396,10 @@ impl Components {
352396

353397
ComponentId(*index)
354398
}
399+
400+
pub fn iter(&self) -> impl Iterator<Item = &ComponentInfo> + '_ {
401+
self.components.iter()
402+
}
355403
}
356404

357405
/// Records when a component was added and when it was last mutably dereferenced (or added).

crates/bevy_ecs/src/storage/blob_vec.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ impl BlobVec {
8686
self.capacity
8787
}
8888

89+
#[inline]
90+
pub fn layout(&self) -> Layout {
91+
self.item_layout
92+
}
93+
8994
pub fn reserve_exact(&mut self, additional: usize) {
9095
let available_space = self.capacity - self.len;
9196
if available_space < additional {

crates/bevy_ecs/src/world/entity_ref.rs

Lines changed: 147 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::{
22
archetype::{Archetype, ArchetypeId, Archetypes},
33
bundle::{Bundle, BundleInfo},
4-
change_detection::Ticks,
4+
change_detection::{MutUntyped, Ticks},
55
component::{Component, ComponentId, ComponentTicks, Components, StorageType},
66
entity::{Entities, Entity, EntityLocation},
77
storage::{SparseSet, Storages},
@@ -103,14 +103,31 @@ impl<'w> EntityRef<'w> {
103103
.map(|(value, ticks)| Mut {
104104
value: value.assert_unique().deref_mut::<T>(),
105105
ticks: Ticks {
106-
component_ticks: &mut *ticks.get(),
106+
component_ticks: ticks.deref_mut(),
107107
last_change_tick,
108108
change_tick,
109109
},
110110
})
111111
}
112112
}
113113

114+
impl<'w> EntityRef<'w> {
115+
/// Gets the component of the given [`ComponentId`] from the entity.
116+
///
117+
/// **You should prefer to use the typed API where possible and only
118+
/// use this in cases where the actual component types are not known at
119+
/// compile time.**
120+
///
121+
/// Unlike [`EntityRef::get`], this returns a raw pointer to the component,
122+
/// which is only valid while the `'w` borrow of the lifetime is active.
123+
#[inline]
124+
pub fn get_by_id(&self, component_id: ComponentId) -> Option<Ptr<'w>> {
125+
self.world.components().get_info(component_id)?;
126+
// SAFE: entity_location is valid, component_id is valid as checked by the line above
127+
unsafe { get_component(self.world, component_id, self.entity, self.location) }
128+
}
129+
}
130+
114131
/// A mutable reference to a particular [`Entity`] and all of its components
115132
pub struct EntityMut<'w> {
116133
world: &'w mut World,
@@ -207,7 +224,7 @@ impl<'w> EntityMut<'w> {
207224
.map(|(value, ticks)| Mut {
208225
value: value.assert_unique().deref_mut::<T>(),
209226
ticks: Ticks {
210-
component_ticks: &mut *ticks.get(),
227+
component_ticks: ticks.deref_mut(),
211228
last_change_tick: self.world.last_change_tick(),
212229
change_tick: self.world.read_change_tick(),
213230
},
@@ -488,14 +505,47 @@ impl<'w> EntityMut<'w> {
488505
}
489506
}
490507

508+
impl<'w> EntityMut<'w> {
509+
/// Gets the component of the given [`ComponentId`] from the entity.
510+
///
511+
/// **You should prefer to use the typed API [`EntityMut::get`] where possible and only
512+
/// use this in cases where the actual component types are not known at
513+
/// compile time.**
514+
///
515+
/// Unlike [`EntityMut::get`], this returns a raw pointer to the component,
516+
/// which is only valid while the [`EntityMut`] is alive.
517+
#[inline]
518+
pub fn get_by_id(&self, component_id: ComponentId) -> Option<Ptr<'_>> {
519+
self.world.components().get_info(component_id)?;
520+
// SAFE: entity_location is valid, component_id is valid as checked by the line above
521+
unsafe { get_component(self.world, component_id, self.entity, self.location) }
522+
}
523+
524+
/// Gets a [`MutUntyped`] of the component of the given [`ComponentId`] from the entity.
525+
///
526+
/// **You should prefer to use the typed API [`EntityMut::get_mut`] where possible and only
527+
/// use this in cases where the actual component types are not known at
528+
/// compile time.**
529+
///
530+
/// Unlike [`EntityMut::get_mut`], this returns a raw pointer to the component,
531+
/// which is only valid while the [`EntityMut`] is alive.
532+
#[inline]
533+
pub fn get_mut_by_id(&mut self, component_id: ComponentId) -> Option<MutUntyped<'_>> {
534+
self.world.components().get_info(component_id)?;
535+
// SAFE: entity_location is valid, component_id is valid as checked by the line above
536+
unsafe { get_mut_by_id(self.world, self.entity, self.location, component_id) }
537+
}
538+
}
539+
491540
// TODO: move to Storages?
492541
/// Get a raw pointer to a particular [`Component`] on a particular [`Entity`] in the provided [`World`].
493542
///
494543
/// # Safety
495-
/// `entity_location` must be within bounds of the given archetype and `entity` must exist inside
544+
/// - `entity_location` must be within bounds of the given archetype and `entity` must exist inside
496545
/// the archetype
546+
/// - `component_id` must be valid
497547
#[inline]
498-
unsafe fn get_component(
548+
pub(crate) unsafe fn get_component(
499549
world: &World,
500550
component_id: ComponentId,
501551
entity: Entity,
@@ -808,16 +858,41 @@ pub(crate) unsafe fn get_mut<T: Component>(
808858
|(value, ticks)| Mut {
809859
value: value.assert_unique().deref_mut::<T>(),
810860
ticks: Ticks {
811-
component_ticks: &mut *ticks.get(),
861+
component_ticks: ticks.deref_mut(),
812862
last_change_tick,
813863
change_tick,
814864
},
815865
},
816866
)
817867
}
818868

869+
// SAFETY: EntityLocation must be valid, component_id must be valid
870+
#[inline]
871+
pub(crate) unsafe fn get_mut_by_id(
872+
world: &mut World,
873+
entity: Entity,
874+
location: EntityLocation,
875+
component_id: ComponentId,
876+
) -> Option<MutUntyped> {
877+
// SAFE: world access is unique, entity location and component_id required to be valid
878+
get_component_and_ticks(world, component_id, entity, location).map(|(value, ticks)| {
879+
MutUntyped {
880+
value: value.assert_unique(),
881+
ticks: Ticks {
882+
component_ticks: ticks.deref_mut(),
883+
last_change_tick: world.last_change_tick(),
884+
change_tick: world.read_change_tick(),
885+
},
886+
}
887+
})
888+
}
889+
819890
#[cfg(test)]
820891
mod tests {
892+
use crate as bevy_ecs;
893+
use crate::component::ComponentId;
894+
use crate::prelude::*; // for the `#[derive(Component)]`
895+
821896
#[test]
822897
fn sorted_remove() {
823898
let mut a = vec![1, 2, 3, 4, 5, 6, 7];
@@ -838,4 +913,70 @@ mod tests {
838913

839914
assert_eq!(a, vec![1]);
840915
}
916+
917+
#[derive(Component)]
918+
struct TestComponent(u32);
919+
920+
#[test]
921+
fn entity_ref_get_by_id() {
922+
let mut world = World::new();
923+
let entity = world.spawn().insert(TestComponent(42)).id();
924+
let component_id = world
925+
.components()
926+
.get_id(std::any::TypeId::of::<TestComponent>())
927+
.unwrap();
928+
929+
let entity = world.entity(entity);
930+
let test_component = entity.get_by_id(component_id).unwrap();
931+
// SAFE: points to a valid `TestComponent`
932+
let test_component = unsafe { test_component.deref::<TestComponent>() };
933+
934+
assert_eq!(test_component.0, 42);
935+
}
936+
937+
#[test]
938+
fn entity_mut_get_by_id() {
939+
let mut world = World::new();
940+
let entity = world.spawn().insert(TestComponent(42)).id();
941+
let component_id = world
942+
.components()
943+
.get_id(std::any::TypeId::of::<TestComponent>())
944+
.unwrap();
945+
946+
let mut entity_mut = world.entity_mut(entity);
947+
let mut test_component = entity_mut.get_mut_by_id(component_id).unwrap();
948+
{
949+
test_component.set_changed();
950+
// SAFE: `test_component` has unique access of the `EntityMut` and is not used afterwards
951+
let test_component =
952+
unsafe { test_component.into_inner().deref_mut::<TestComponent>() };
953+
test_component.0 = 43;
954+
}
955+
956+
let entity = world.entity(entity);
957+
let test_component = entity.get_by_id(component_id).unwrap();
958+
let test_component = unsafe { test_component.deref::<TestComponent>() };
959+
960+
assert_eq!(test_component.0, 43);
961+
}
962+
963+
#[test]
964+
fn entity_ref_get_by_id_invalid_component_id() {
965+
let invalid_component_id = ComponentId::new(usize::MAX);
966+
967+
let mut world = World::new();
968+
let entity = world.spawn().id();
969+
let entity = world.entity(entity);
970+
assert!(entity.get_by_id(invalid_component_id).is_none());
971+
}
972+
973+
#[test]
974+
fn entity_mut_get_by_id_invalid_component_id() {
975+
let invalid_component_id = ComponentId::new(usize::MAX);
976+
977+
let mut world = World::new();
978+
let mut entity = world.spawn();
979+
assert!(entity.get_by_id(invalid_component_id).is_none());
980+
assert!(entity.get_mut_by_id(invalid_component_id).is_none());
981+
}
841982
}

0 commit comments

Comments
 (0)