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

Automatic registration of ECS types #12332

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b14896a
Automatically register Component types on initialization
james7132 Mar 6, 2024
3d5f60d
Make AppTypeRegistry use the World internal one
james7132 Mar 6, 2024
5a9c04b
Merge branch 'main' into automatic-ecs-registration
james7132 Mar 6, 2024
fc1b308
Fix dynamic_scene_builder tests
james7132 Mar 6, 2024
9c388a5
Formatting
james7132 Mar 6, 2024
91354fd
Fix typo.
james7132 Mar 6, 2024
c19bda8
Cleanup proc macro code
james7132 Mar 6, 2024
cc7c253
Target reflect(Component) instead
james7132 Mar 6, 2024
51a2f08
Support automatically registering resources
james7132 Mar 6, 2024
b615535
Fix up tests
james7132 Mar 6, 2024
512593e
Cleanup + disable generics
james7132 Mar 7, 2024
20dde15
Minimize what we're reexporting from the private module
james7132 Mar 7, 2024
5fdea45
Merge branch 'main' into automatic-ecs-registration
james7132 Mar 8, 2024
3e1b0b1
Fix tests
james7132 Mar 8, 2024
ccd8405
Fix deadlock by not panicking if already registered.
james7132 Mar 8, 2024
20e18ac
Fix tests
james7132 Mar 8, 2024
3013029
Fix more tests
james7132 Mar 8, 2024
01725d9
Merge branch 'main' into automatic-ecs-registration
james7132 Mar 8, 2024
4558439
Remove unnecessary registrations
james7132 Mar 9, 2024
5aa2fa9
Cleanup generated code with a shim
james7132 Mar 9, 2024
a8950a6
Cleanup has_reflect_attr
james7132 Mar 9, 2024
47ef511
Cleanup macro logic
james7132 Mar 9, 2024
54809cf
More cleanup
james7132 Mar 9, 2024
8e44f11
then_some
james7132 Mar 9, 2024
4f07b1f
Update example for scenes
james7132 Mar 9, 2024
9a42c8b
Provide crate level docs
james7132 Mar 9, 2024
89bddcb
AppTypeRegistry is already in bevy_ecs
james7132 Mar 9, 2024
807f947
Make AppTypeRegistry only constructable via FromWorld
james7132 Mar 9, 2024
000f939
Add registration test for resources
james7132 Mar 9, 2024
91ad713
Fix tests
james7132 Mar 9, 2024
9fda301
Merge branch 'main' into automatic-ecs-registration
james7132 Mar 30, 2024
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
1 change: 1 addition & 0 deletions crates/bevy_ecs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ categories = ["game-engines", "data-structures"]
trace = []
multi-threaded = ["bevy_tasks/multi-threaded"]
bevy_debug_stepping = []
bevy_reflect = ["dep:bevy_reflect", "bevy_ecs_macros/bevy_reflect"]
default = ["bevy_reflect", "bevy_debug_stepping"]

[dependencies]
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_ecs/macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ license = "MIT OR Apache-2.0"
[lib]
proc-macro = true

[features]
bevy_reflect = []

[dependencies]
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.14.0-dev" }

Expand Down
38 changes: 34 additions & 4 deletions crates/bevy_ecs/macros/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,35 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();

TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
#[cfg(not(feature = "bevy_reflect"))]
{
TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
}
})
}
#[cfg(feature = "bevy_reflect")]
{
if has_reflect_attr(&ast) {
TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;

#[doc(hidden)]
fn __register_type(registry: &#bevy_ecs_path::private::bevy_reflect::TypeRegistryArc) {
registry.write().register::<Self>();
}
}
})
} else {
TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
}
})
}
})
}
james7132 marked this conversation as resolved.
Show resolved Hide resolved
}

pub const COMPONENT: &str = "component";
Expand Down Expand Up @@ -109,6 +133,12 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
Ok(attrs)
}

#[cfg(feature = "bevy_reflect")]
fn has_reflect_attr(ast: &DeriveInput) -> bool {
const REFLECT: &str = "reflect";
ast.attrs.iter().any(|a| a.path().is_ident(REFLECT))
}
james7132 marked this conversation as resolved.
Show resolved Hide resolved

fn storage_path(bevy_ecs_path: &Path, ty: StorageTy) -> TokenStream2 {
let storage_type = match ty {
StorageTy::Table => Ident::new("Table", Span::call_site()),
Expand Down
37 changes: 36 additions & 1 deletion crates/bevy_ecs/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::{
pub use bevy_ecs_macros::Component;
use bevy_ptr::{OwningPtr, UnsafeCellDeref};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
use bevy_reflect::{Reflect, TypeRegistryArc};
use bevy_utils::TypeIdMap;
use std::cell::UnsafeCell;
use std::{
Expand Down Expand Up @@ -156,6 +156,13 @@ pub trait Component: Send + Sync + 'static {

/// Called when registering this component, allowing mutable access to it's [`ComponentHooks`].
fn register_component_hooks(_hooks: &mut ComponentHooks) {}

/// Shim for automatically registering components. Intentionally hidden as it's not part of the
/// public interface. Only available if the `bevy_reflect` feature is enabled.
#[doc(hidden)]
#[allow(unused_variables)]
#[cfg(feature = "bevy_reflect")]
fn __register_type(registry: &TypeRegistryArc) {}
}

/// The storage used for a specific component type.
Expand Down Expand Up @@ -525,6 +532,8 @@ pub struct Components {
components: Vec<ComponentInfo>,
indices: TypeIdMap<ComponentId>,
resource_indices: TypeIdMap<ComponentId>,
#[cfg(feature = "bevy_reflect")]
pub(crate) type_registry: TypeRegistryArc,
}

impl Components {
Expand Down Expand Up @@ -552,6 +561,8 @@ impl Components {
ComponentDescriptor::new::<T>(),
);
T::register_component_hooks(&mut components[index.index()].hooks);
#[cfg(feature = "bevy_reflect")]
T::__register_type(&self.type_registry);
index
})
}
Expand Down Expand Up @@ -971,3 +982,27 @@ impl<T: Component> FromWorld for InitComponentId<T> {
}
}
}

#[cfg(test)]
mod test {
#[test]
#[cfg(feature = "bevy_reflect")]
fn init_component_registers_component() {
use crate as bevy_ecs;
use crate::{
component::Components, prelude::Component, reflect::ReflectComponent, storage::Storages,
};
use bevy_reflect::Reflect;
use core::any::TypeId;

#[derive(Component, Reflect)]
#[reflect(Component)]
struct A(usize);

let mut components = Components::default();
let mut storages = Storages::default();
components.init_component::<A>(&mut storages);

assert!(components.type_registry.read().contains(TypeId::of::<A>()));
}
}
8 changes: 8 additions & 0 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ pub mod prelude {
};
}

#[cfg(feature = "bevy_reflect")]
#[doc(hidden)]
pub mod private {
pub mod bevy_reflect {
pub use bevy_reflect::*;
}
}

pub use bevy_utils::all_tuples;

#[cfg(test)]
Expand Down
13 changes: 11 additions & 2 deletions crates/bevy_ecs/src/reflect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ use std::any::TypeId;
use std::ops::{Deref, DerefMut};

use crate as bevy_ecs;
use crate::{system::Resource, world::World};
use crate::{
system::Resource,
world::{FromWorld, World},
};
use bevy_reflect::{FromReflect, Reflect, TypeRegistry, TypeRegistryArc};

mod bundle;
Expand All @@ -23,7 +26,7 @@ pub use resource::{ReflectResource, ReflectResourceFns};

/// A [`Resource`] storing [`TypeRegistry`] for
/// type registrations relevant to a whole app.
#[derive(Resource, Clone, Default)]
#[derive(Resource, Clone)]
pub struct AppTypeRegistry(pub TypeRegistryArc);

impl Deref for AppTypeRegistry {
Expand All @@ -42,6 +45,12 @@ impl DerefMut for AppTypeRegistry {
}
}

impl FromWorld for AppTypeRegistry {
fn from_world(world: &mut World) -> Self {
Self(world.__type_registry().clone())
}
}

/// Creates a `T` from a `&dyn Reflect`.
///
/// The first approach uses `T`'s implementation of `FromReflect`.
Expand Down
8 changes: 8 additions & 0 deletions crates/bevy_ecs/src/world/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ use std::{
sync::atomic::{AtomicU32, Ordering},
};
mod identifier;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::TypeRegistryArc;

use self::unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell};
pub use identifier::WorldId;
Expand Down Expand Up @@ -153,6 +155,12 @@ impl World {
World::default()
}

#[doc(hidden)]
#[cfg(feature = "bevy_reflect")]
pub fn __type_registry(&self) -> &TypeRegistryArc {
&self.components().type_registry
}

/// Retrieves this [`World`]'s unique ID
#[inline]
pub fn id(&self) -> WorldId {
Expand Down
74 changes: 37 additions & 37 deletions crates/bevy_scene/src/dynamic_scene_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,9 +380,11 @@ mod tests {
fn extract_one_entity() {
let mut world = World::default();

let atr = AppTypeRegistry::default();
atr.write().register::<ComponentA>();
world.insert_resource(atr);
world.init_resource::<AppTypeRegistry>();
{
let mut register = world.resource::<AppTypeRegistry>().write();
register.register::<ComponentA>();
}

let entity = world.spawn((ComponentA, ComponentB)).id();

Expand All @@ -400,9 +402,11 @@ mod tests {
fn extract_one_entity_twice() {
let mut world = World::default();

let atr = AppTypeRegistry::default();
atr.write().register::<ComponentA>();
world.insert_resource(atr);
world.init_resource::<AppTypeRegistry>();
{
let mut register = world.resource::<AppTypeRegistry>().write();
register.register::<ComponentA>();
}

let entity = world.spawn((ComponentA, ComponentB)).id();

Expand All @@ -421,13 +425,12 @@ mod tests {
fn extract_one_entity_two_components() {
let mut world = World::default();

let atr = AppTypeRegistry::default();
world.init_resource::<AppTypeRegistry>();
{
let mut register = atr.write();
let mut register = world.resource::<AppTypeRegistry>().write();
register.register::<ComponentA>();
register.register::<ComponentB>();
}
world.insert_resource(atr);

let entity = world.spawn((ComponentA, ComponentB)).id();

Expand Down Expand Up @@ -472,13 +475,12 @@ mod tests {
fn extract_query() {
let mut world = World::default();

let atr = AppTypeRegistry::default();
world.init_resource::<AppTypeRegistry>();
{
let mut register = atr.write();
let mut register = world.resource::<AppTypeRegistry>().write();
register.register::<ComponentA>();
register.register::<ComponentB>();
}
world.insert_resource(atr);

let entity_a_b = world.spawn((ComponentA, ComponentB)).id();
let entity_a = world.spawn(ComponentA).id();
Expand All @@ -499,9 +501,11 @@ mod tests {
fn remove_componentless_entity() {
let mut world = World::default();

let atr = AppTypeRegistry::default();
atr.write().register::<ComponentA>();
world.insert_resource(atr);
world.init_resource::<AppTypeRegistry>();
{
let mut register = world.resource::<AppTypeRegistry>().write();
register.register::<ComponentA>();
}

let entity_a = world.spawn(ComponentA).id();
let entity_b = world.spawn(ComponentB).id();
Expand All @@ -519,9 +523,11 @@ mod tests {
fn extract_one_resource() {
let mut world = World::default();

let atr = AppTypeRegistry::default();
atr.write().register::<ResourceA>();
world.insert_resource(atr);
world.init_resource::<AppTypeRegistry>();
{
let mut register = world.resource::<AppTypeRegistry>().write();
register.register::<ResourceA>();
}

world.insert_resource(ResourceA);

Expand All @@ -537,9 +543,11 @@ mod tests {
fn extract_one_resource_twice() {
let mut world = World::default();

let atr = AppTypeRegistry::default();
atr.write().register::<ResourceA>();
world.insert_resource(atr);
world.init_resource::<AppTypeRegistry>();
{
let mut register = world.resource::<AppTypeRegistry>().write();
register.register::<ResourceA>();
}

world.insert_resource(ResourceA);

Expand All @@ -555,14 +563,12 @@ mod tests {
#[test]
fn should_extract_allowed_components() {
let mut world = World::default();

let atr = AppTypeRegistry::default();
world.init_resource::<AppTypeRegistry>();
{
let mut register = atr.write();
let mut register = world.resource::<AppTypeRegistry>().write();
register.register::<ComponentA>();
register.register::<ComponentB>();
}
world.insert_resource(atr);

let entity_a_b = world.spawn((ComponentA, ComponentB)).id();
let entity_a = world.spawn(ComponentA).id();
Expand All @@ -582,14 +588,12 @@ mod tests {
#[test]
fn should_not_extract_denied_components() {
let mut world = World::default();

let atr = AppTypeRegistry::default();
world.init_resource::<AppTypeRegistry>();
{
let mut register = atr.write();
let mut register = world.resource::<AppTypeRegistry>().write();
register.register::<ComponentA>();
register.register::<ComponentB>();
}
world.insert_resource(atr);

let entity_a_b = world.spawn((ComponentA, ComponentB)).id();
let entity_a = world.spawn(ComponentA).id();
Expand All @@ -609,14 +613,12 @@ mod tests {
#[test]
fn should_extract_allowed_resources() {
let mut world = World::default();

let atr = AppTypeRegistry::default();
world.init_resource::<AppTypeRegistry>();
{
let mut register = atr.write();
let mut register = world.resource::<AppTypeRegistry>().write();
register.register::<ResourceA>();
register.register::<ResourceB>();
}
world.insert_resource(atr);

world.insert_resource(ResourceA);
world.insert_resource(ResourceB);
Expand All @@ -633,14 +635,12 @@ mod tests {
#[test]
fn should_not_extract_denied_resources() {
let mut world = World::default();

let atr = AppTypeRegistry::default();
world.init_resource::<AppTypeRegistry>();
{
let mut register = atr.write();
let mut register = world.resource::<AppTypeRegistry>().write();
register.register::<ResourceA>();
register.register::<ResourceB>();
}
world.insert_resource(atr);

world.insert_resource(ResourceA);
world.insert_resource(ResourceB);
Expand Down
Loading