Skip to content

Commit

Permalink
Make System responsible for updating its own archetypes (bevyengine…
Browse files Browse the repository at this point in the history
…#4115)

# Objective

- Make it possible to use `System`s outside of the scheduler/executor without having to define logic to track new archetypes and call `System::add_archetype()` for each.

## Solution

- Replace `System::add_archetype(&Archetype)` with `System::update_archetypes(&World)`, making systems responsible for tracking their own most recent archetype generation the way that `SystemState` already does.

This has minimal (or simplifying) effect on most of the code with the exception of `FunctionSystem`, which must now track the latest `ArchetypeGeneration` it saw instead of relying on the executor to do it.

Co-authored-by: Carter Anderson <[email protected]>
  • Loading branch information
2 people authored and ItsDoot committed Feb 1, 2023
1 parent 288bd27 commit af015c7
Show file tree
Hide file tree
Showing 15 changed files with 264 additions and 159 deletions.
7 changes: 6 additions & 1 deletion benches/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@ license = "MIT OR Apache-2.0"

[dev-dependencies]
glam = "0.20"
criterion = "0.3"
criterion = { version = "0.3", features = ["html_reports"] }
bevy_ecs = { path = "../crates/bevy_ecs" }
bevy_tasks = { path = "../crates/bevy_tasks" }

[[bench]]
name = "archetype_updates"
path = "benches/bevy_ecs/archetype_updates.rs"
harness = false

[[bench]]
name = "ecs_bench_suite"
path = "benches/bevy_ecs/ecs_bench_suite/mod.rs"
Expand Down
119 changes: 119 additions & 0 deletions benches/benches/bevy_ecs/archetype_updates.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use bevy_ecs::{
component::Component,
schedule::{Stage, SystemStage},
world::World,
};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};

criterion_group!(benches, no_archetypes, added_archetypes);
criterion_main!(benches);

#[derive(Component)]
struct A<const N: u16>(f32);

fn setup(system_count: usize) -> (World, SystemStage) {
let mut world = World::new();
fn empty() {}
let mut stage = SystemStage::parallel();
for _ in 0..system_count {
stage.add_system(empty);
}
stage.run(&mut world);
(world, stage)
}

/// create `count` entities with distinct archetypes
fn add_archetypes(world: &mut World, count: u16) {
for i in 0..count {
let mut e = world.spawn();
if i & 1 << 0 != 0 {
e.insert(A::<0>(1.0));
}
if i & 1 << 1 != 0 {
e.insert(A::<1>(1.0));
}
if i & 1 << 2 != 0 {
e.insert(A::<2>(1.0));
}
if i & 1 << 3 != 0 {
e.insert(A::<3>(1.0));
}
if i & 1 << 4 != 0 {
e.insert(A::<4>(1.0));
}
if i & 1 << 5 != 0 {
e.insert(A::<5>(1.0));
}
if i & 1 << 6 != 0 {
e.insert(A::<6>(1.0));
}
if i & 1 << 7 != 0 {
e.insert(A::<7>(1.0));
}
if i & 1 << 8 != 0 {
e.insert(A::<8>(1.0));
}
if i & 1 << 9 != 0 {
e.insert(A::<9>(1.0));
}
if i & 1 << 10 != 0 {
e.insert(A::<10>(1.0));
}
if i & 1 << 11 != 0 {
e.insert(A::<11>(1.0));
}
if i & 1 << 12 != 0 {
e.insert(A::<12>(1.0));
}
if i & 1 << 13 != 0 {
e.insert(A::<13>(1.0));
}
if i & 1 << 14 != 0 {
e.insert(A::<14>(1.0));
}
if i & 1 << 15 != 0 {
e.insert(A::<15>(1.0));
}
}
}

fn no_archetypes(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("no_archetypes");
for i in 0..=5 {
let system_count = i * 20;
let (mut world, mut stage) = setup(system_count);
group.bench_with_input(
BenchmarkId::new("system_count", system_count),
&system_count,
|bencher, &_system_count| {
bencher.iter(|| {
stage.run(&mut world);
});
},
);
}
}

fn added_archetypes(criterion: &mut Criterion) {
const SYSTEM_COUNT: usize = 100;
let mut group = criterion.benchmark_group("added_archetypes");
for archetype_count in [100, 200, 500, 1000, 2000, 5000, 10000] {
group.bench_with_input(
BenchmarkId::new("archetype_count", archetype_count),
&archetype_count,
|bencher, &archetype_count| {
bencher.iter_batched(
|| {
let (mut world, stage) = setup(SYSTEM_COUNT);
add_archetypes(&mut world, archetype_count);
(world, stage)
},
|(mut world, mut stage)| {
stage.run(&mut world);
},
criterion::BatchSize::LargeInput,
)
},
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ impl Benchmark {

let mut system = IntoSystem::into_system(query_system);
system.initialize(&mut world);
for archetype in world.archetypes().iter() {
system.new_archetype(archetype);
}
system.update_archetype_component_access(&world);
Self(world, entity, Box::new(system))
}

Expand Down
4 changes: 1 addition & 3 deletions benches/benches/bevy_ecs/ecs_bench_suite/heavy_compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ impl Benchmark {
world.insert_resource(TaskPool::default());
let mut system = IntoSystem::into_system(sys);
system.initialize(&mut world);
for archetype in world.archetypes().iter() {
system.new_archetype(archetype);
}
system.update_archetype_component_access(&world);

Self(world, Box::new(system))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ impl Benchmark {

let mut system = IntoSystem::into_system(query_system);
system.initialize(&mut world);
for archetype in world.archetypes().iter() {
system.new_archetype(archetype);
}
system.update_archetype_component_access(&world);
Self(world, Box::new(system))
}

Expand Down
11 changes: 6 additions & 5 deletions crates/bevy_core/src/time/fixed_timestep.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::Time;
use bevy_ecs::{
archetype::{Archetype, ArchetypeComponentId},
archetype::ArchetypeComponentId,
component::ComponentId,
query::Access,
schedule::ShouldRun,
Expand Down Expand Up @@ -177,10 +177,6 @@ impl System for FixedTimestep {
Cow::Borrowed(std::any::type_name::<FixedTimestep>())
}

fn new_archetype(&mut self, archetype: &Archetype) {
self.internal_system.new_archetype(archetype);
}

fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
self.internal_system.archetype_component_access()
}
Expand Down Expand Up @@ -220,6 +216,11 @@ impl System for FixedTimestep {
}
}

fn update_archetype_component_access(&mut self, world: &World) {
self.internal_system
.update_archetype_component_access(world);
}

fn check_change_tick(&mut self, change_tick: u32) {
self.internal_system.check_change_tick(change_tick);
}
Expand Down
35 changes: 3 additions & 32 deletions crates/bevy_ecs/src/schedule/executor.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{archetype::ArchetypeGeneration, schedule::ParallelSystemContainer, world::World};
use crate::{schedule::ParallelSystemContainer, world::World};
use downcast_rs::{impl_downcast, Downcast};

pub trait ParallelSystemExecutor: Downcast + Send + Sync {
Expand All @@ -10,24 +10,13 @@ pub trait ParallelSystemExecutor: Downcast + Send + Sync {

impl_downcast!(ParallelSystemExecutor);

pub struct SingleThreadedExecutor {
/// Last archetypes generation observed by parallel systems.
archetype_generation: ArchetypeGeneration,
}
#[derive(Default)]
pub struct SingleThreadedExecutor;

impl Default for SingleThreadedExecutor {
fn default() -> Self {
Self {
archetype_generation: ArchetypeGeneration::initial(),
}
}
}
impl ParallelSystemExecutor for SingleThreadedExecutor {
fn rebuild_cached_data(&mut self, _: &[ParallelSystemContainer]) {}

fn run_systems(&mut self, systems: &mut [ParallelSystemContainer], world: &mut World) {
self.update_archetypes(systems, world);

for system in systems {
if system.should_run() {
#[cfg(feature = "trace")]
Expand All @@ -38,21 +27,3 @@ impl ParallelSystemExecutor for SingleThreadedExecutor {
}
}
}

impl SingleThreadedExecutor {
/// Calls `system.new_archetype()` for each archetype added since the last call to
/// `update_archetypes` and updates cached `archetype_component_access`.
fn update_archetypes(&mut self, systems: &mut [ParallelSystemContainer], world: &World) {
let archetypes = world.archetypes();
let new_generation = archetypes.generation();
let old_generation = std::mem::replace(&mut self.archetype_generation, new_generation);
let archetype_index_range = old_generation.value()..new_generation.value();

for archetype in archetypes.archetypes[archetype_index_range].iter() {
for container in systems.iter_mut() {
let system = container.system_mut();
system.new_archetype(archetype);
}
}
}
}
38 changes: 12 additions & 26 deletions crates/bevy_ecs/src/schedule/executor_parallel.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
archetype::{ArchetypeComponentId, ArchetypeGeneration},
archetype::ArchetypeComponentId,
query::Access,
schedule::{ParallelSystemContainer, ParallelSystemExecutor},
world::World,
Expand Down Expand Up @@ -32,8 +32,6 @@ struct SystemSchedulingMetadata {
}

pub struct ParallelExecutor {
/// Last archetypes generation observed by parallel systems.
archetype_generation: ArchetypeGeneration,
/// Cached metadata of every system.
system_metadata: Vec<SystemSchedulingMetadata>,
/// Used by systems to notify the executor that they have finished.
Expand All @@ -60,7 +58,6 @@ impl Default for ParallelExecutor {
fn default() -> Self {
let (finish_sender, finish_receiver) = async_channel::unbounded();
Self {
archetype_generation: ArchetypeGeneration::initial(),
system_metadata: Default::default(),
finish_sender,
finish_receiver,
Expand Down Expand Up @@ -114,7 +111,17 @@ impl ParallelSystemExecutor for ParallelExecutor {
self.events_sender = Some(sender);
}

self.update_archetypes(systems, world);
{
#[cfg(feature = "trace")]
let _span = bevy_utils::tracing::info_span!("update_archetypes").entered();
for (index, container) in systems.iter_mut().enumerate() {
let meta = &mut self.system_metadata[index];
let system = container.system_mut();
system.update_archetype_component_access(world);
meta.archetype_component_access
.extend(system.archetype_component_access());
}
}

let compute_pool = world
.get_resource_or_insert_with(|| ComputeTaskPool(TaskPool::default()))
Expand Down Expand Up @@ -154,27 +161,6 @@ impl ParallelSystemExecutor for ParallelExecutor {
}

impl ParallelExecutor {
/// Calls `system.new_archetype()` for each archetype added since the last call to
/// `update_archetypes` and updates cached `archetype_component_access`.
fn update_archetypes(&mut self, systems: &mut [ParallelSystemContainer], world: &World) {
#[cfg(feature = "trace")]
let _span = bevy_utils::tracing::info_span!("update_archetypes").entered();
let archetypes = world.archetypes();
let new_generation = archetypes.generation();
let old_generation = std::mem::replace(&mut self.archetype_generation, new_generation);
let archetype_index_range = old_generation.value()..new_generation.value();

for archetype in archetypes.archetypes[archetype_index_range].iter() {
for (index, container) in systems.iter_mut().enumerate() {
let meta = &mut self.system_metadata[index];
let system = container.system_mut();
system.new_archetype(archetype);
meta.archetype_component_access
.extend(system.archetype_component_access());
}
}
}

/// Populates `should_run` bitset, spawns tasks for systems that should run this iteration,
/// queues systems with no dependencies to run (or skip) at next opportunity.
fn prepare_systems<'scope>(
Expand Down
Loading

0 comments on commit af015c7

Please sign in to comment.