Skip to content

Fragmenting value components #19153

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ keywords = ["game", "engine", "gamedev", "graphics", "bevy"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/bevyengine/bevy"
documentation = "https://docs.rs/bevy"
rust-version = "1.85.0"
rust-version = "1.86.0"

[workspace]
resolver = "2"
Expand Down
123 changes: 123 additions & 0 deletions benches/benches/bevy_ecs/components/fragmenting_values.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use bevy_ecs::prelude::*;
use criterion::Criterion;
use glam::*;

#[derive(Component, PartialEq, Eq, Hash, Clone)]
#[component(immutable, key=Self)]
struct Fragmenting<const N: usize>(u32);

#[derive(Component)]
struct NonFragmenting<const N: usize>(Vec3);

pub fn insert_fragmenting_value(c: &mut Criterion) {
let mut group = c.benchmark_group("insert_fragmenting_value");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(5));
group.bench_function("base", |b| {
b.iter(move || {
let mut world = World::new();
world.spawn_batch((0..10_000).map(|_| {
(
Fragmenting::<1>(1),
NonFragmenting::<1>(Vec3::ONE),
NonFragmenting::<2>(Vec3::ONE),
NonFragmenting::<3>(Vec3::ONE),
)
}));
});
});
group.bench_function("unbatched", |b| {
b.iter(move || {
let mut world = World::new();
for _ in 0..10_000 {
world.spawn((
Fragmenting::<1>(1),
NonFragmenting::<1>(Vec3::ONE),
NonFragmenting::<2>(Vec3::ONE),
NonFragmenting::<3>(Vec3::ONE),
));
}
});
});
group.bench_function("high_fragmentation_base", |b| {
b.iter(move || {
let mut world = World::new();
world.spawn_batch((0..10_000).map(|i| {
(
Fragmenting::<1>(i % 100),
NonFragmenting::<1>(Vec3::ONE),
NonFragmenting::<2>(Vec3::ONE),
NonFragmenting::<3>(Vec3::ONE),
)
}));
});
});
group.bench_function("high_fragmentation_unbatched", |b| {
b.iter(move || {
let mut world = World::new();
for i in 0..10_000 {
world.spawn((
Fragmenting::<1>(i % 100),
NonFragmenting::<1>(Vec3::ONE),
NonFragmenting::<2>(Vec3::ONE),
NonFragmenting::<3>(Vec3::ONE),
));
}
});
});
group.finish();
}

pub fn add_remove_fragmenting_value(c: &mut Criterion) {
let mut group = c.benchmark_group("add_remove_fragmenting_value");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(5));

group.bench_function("non_fragmenting", |b| {
let mut world = World::new();
let entities: Vec<_> = world
.spawn_batch((0..10_000).map(|_| {
(
Fragmenting::<1>(1),
NonFragmenting::<1>(Vec3::ONE),
NonFragmenting::<2>(Vec3::ONE),
NonFragmenting::<3>(Vec3::ONE),
)
}))
.collect();
b.iter(move || {
for entity in &entities {
world
.entity_mut(*entity)
.insert(NonFragmenting::<4>(Vec3::ZERO));
}

for entity in &entities {
world.entity_mut(*entity).remove::<NonFragmenting<4>>();
}
});
});

group.bench_function("fragmenting", |b| {
let mut world = World::new();
let entities: Vec<_> = world
.spawn_batch((0..10_000).map(|_| {
(
Fragmenting::<1>(1),
NonFragmenting::<1>(Vec3::ONE),
NonFragmenting::<2>(Vec3::ONE),
NonFragmenting::<3>(Vec3::ONE),
)
}))
.collect();
b.iter(move || {
for entity in &entities {
world.entity_mut(*entity).insert(Fragmenting::<1>(2));
}

for entity in &entities {
world.entity_mut(*entity).remove::<NonFragmenting<1>>();
}
});
});
}
4 changes: 4 additions & 0 deletions benches/benches/bevy_ecs/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ mod add_remove_sparse_set;
mod add_remove_table;
mod add_remove_very_big_table;
mod archetype_updates;
mod fragmenting_values;
mod insert_simple;
mod insert_simple_unbatched;

use archetype_updates::*;
use criterion::{criterion_group, Criterion};
use fragmenting_values::*;

criterion_group!(
benches,
Expand All @@ -19,6 +21,8 @@ criterion_group!(
insert_simple,
no_archetypes,
added_archetypes,
insert_fragmenting_value,
add_remove_fragmenting_value
);

fn add_remove(c: &mut Criterion) {
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_core_pipeline/src/oit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ impl Default for OrderIndependentTransparencySettings {
impl Component for OrderIndependentTransparencySettings {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
type Mutability = Mutable;
type Key = NoKey<Self>;

fn on_add() -> Option<ComponentHook> {
Some(|world, context| {
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["ecs", "game", "bevy"]
categories = ["game-engines", "data-structures"]
rust-version = "1.85.0"
rust-version = "1.86.0"

[features]
default = ["std", "bevy_reflect", "async_executor", "backtrace"]
Expand Down
13 changes: 13 additions & 0 deletions crates/bevy_ecs/macros/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,13 +260,20 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
)
};

let key = if let Some(key) = attrs.key {
quote! {#bevy_ecs_path::component::OtherComponentKey<Self, #key>}
} else {
quote! {#bevy_ecs_path::component::NoKey<Self>}
};

// This puts `register_required` before `register_recursive_requires` to ensure that the constructors of _all_ top
// level components are initialized first, giving them precedence over recursively defined constructors for the same component type
TokenStream::from(quote! {
#required_component_docs
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
type Mutability = #mutable_type;
type Key = #key;
fn register_required_components(
requiree: #bevy_ecs_path::component::ComponentId,
components: &mut #bevy_ecs_path::component::ComponentsRegistrator,
Expand Down Expand Up @@ -399,6 +406,7 @@ pub const ON_DESPAWN: &str = "on_despawn";

pub const IMMUTABLE: &str = "immutable";
pub const CLONE_BEHAVIOR: &str = "clone_behavior";
pub const KEY: &str = "key";

/// All allowed attribute value expression kinds for component hooks
#[derive(Debug)]
Expand Down Expand Up @@ -462,6 +470,7 @@ struct Attrs {
relationship_target: Option<RelationshipTarget>,
immutable: bool,
clone_behavior: Option<Expr>,
key: Option<Type>,
}

#[derive(Clone, Copy)]
Expand Down Expand Up @@ -501,6 +510,7 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
relationship_target: None,
immutable: false,
clone_behavior: None,
key: None,
};

let mut require_paths = HashSet::new();
Expand Down Expand Up @@ -539,6 +549,9 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
} else if nested.path.is_ident(CLONE_BEHAVIOR) {
attrs.clone_behavior = Some(nested.value()?.parse()?);
Ok(())
} else if nested.path.is_ident(KEY) {
attrs.key = Some(nested.value()?.parse()?);
Ok(())
} else {
Err(nested.error("Unsupported attribute"))
}
Expand Down
26 changes: 26 additions & 0 deletions crates/bevy_ecs/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
let mut field_component_ids = Vec::new();
let mut field_get_component_ids = Vec::new();
let mut field_get_components = Vec::new();
let mut field_get_fragmenting_values = Vec::new();
let mut field_has_fragmenting_values = Vec::new();
let mut field_from_components = Vec::new();
let mut field_required_components = Vec::new();
for (((i, field_type), field_kind), field) in field_type
Expand All @@ -96,6 +98,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
field_get_component_ids.push(quote! {
<#field_type as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids);
});
field_has_fragmenting_values.push(quote! {
<#field_type as #ecs_path::bundle::Bundle>::has_fragmenting_values()
});
match field {
Some(field) => {
field_get_components.push(quote! {
Expand All @@ -104,6 +109,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
field_from_components.push(quote! {
#field: <#field_type as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func),
});
field_get_fragmenting_values.push(quote! {
self.#field.get_fragmenting_values(components, &mut *values);
});
}
None => {
let index = Index::from(i);
Expand All @@ -113,6 +121,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
field_from_components.push(quote! {
#index: <#field_type as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func),
});
field_get_fragmenting_values.push(quote! {
self.#index.get_fragmenting_values(components, &mut *values);
});
}
}
}
Expand Down Expand Up @@ -155,6 +166,21 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
){
#(#field_required_components)*
}

#[allow(unused_variables)]
#[inline]
fn get_fragmenting_values<'a>(
&'a self,
components: &mut #ecs_path::component::ComponentsRegistrator,
values: &mut impl FnMut(#ecs_path::component::ComponentId, &'a dyn #ecs_path::fragmenting_value::FragmentingValue)
) {
#(#field_get_fragmenting_values)*
}

#[inline(always)]
fn has_fragmenting_values() -> bool {
false #(|| #field_has_fragmenting_values)*
}
}

// SAFETY:
Expand Down
Loading