From c8e38c062e70025ae2664643a3d368eec204914e Mon Sep 17 00:00:00 2001 From: Ben Frankel Date: Fri, 8 Dec 2023 20:47:50 -0800 Subject: [PATCH] Optimize entity spawning with an entity pool --- src/physics.rs | 6 +- src/simulation.rs | 139 +++++++++++++++++--------- src/simulation/sprite_pack.rs | 19 ++-- src/state/editor_screen/scene_view.rs | 2 +- src/util.rs | 1 - src/util/despawn.rs | 44 +------- 6 files changed, 107 insertions(+), 104 deletions(-) diff --git a/src/physics.rs b/src/physics.rs index 67cdb95..9f8625e 100644 --- a/src/physics.rs +++ b/src/physics.rs @@ -7,6 +7,7 @@ pub struct PhysicsPlugin; impl Plugin for PhysicsPlugin { fn build(&self, app: &mut App) { app.register_type::() + .register_type::() .init_resource::() .add_systems(Update, apply_velocity.in_set(AppSet::Simulate)); } @@ -14,12 +15,13 @@ impl Plugin for PhysicsPlugin { pub const UNIT_SPEED: f32 = 8.0; -#[derive(Resource, Default)] +#[derive(Resource, Reflect, Default)] +#[reflect(Resource)] pub struct PhysicsSettings { pub speed_multiplier: f32, } -#[derive(Component, Reflect)] +#[derive(Component, Reflect, Default, Clone, Copy)] pub struct Velocity(pub Vec3); fn apply_velocity( diff --git a/src/simulation.rs b/src/simulation.rs index a0e5189..158ae53 100644 --- a/src/simulation.rs +++ b/src/simulation.rs @@ -2,7 +2,9 @@ mod sprite_pack; use std::f32::consts::TAU; +use bevy::ecs::event::ManualEventReader; use bevy::prelude::*; +use rand::thread_rng; use rand::Rng; use crate::physics::Velocity; @@ -14,7 +16,6 @@ use crate::state::editor_screen::SceneViewBounds; use crate::state::editor_screen::WrapWithinSceneView; use crate::state::AppState; use crate::ui::CodeTyper; -use crate::util::OverflowDespawnQueue; use crate::AppRoot; use crate::AppSet; @@ -25,14 +26,12 @@ pub struct SimulationPlugin; impl Plugin for SimulationPlugin { fn build(&self, app: &mut App) { app.register_type::() - .register_type::() .add_plugins(sprite_pack::SpritePackPlugin) .add_event::() .add_event::() .init_resource::() .init_resource::() .init_resource::() - .add_systems(Startup, spawn_entity_caps) .add_systems( Update, ( @@ -112,28 +111,85 @@ impl Default for Simulation { } } -/// Maximum number of entities that can be spawned in the scene view for a single SpawnEvent. -const MAX_SPAWN_PER_EVENT: usize = 32; +struct EntityPool { + entities: Vec, + old_idx: usize, +} -#[derive(Event, Reflect)] +impl Default for EntityPool { + fn default() -> Self { + Self { + entities: Vec::with_capacity(20_000), + old_idx: 0, + } + } +} + +impl EntityPool { + fn recycle(&mut self) -> Entity { + self.old_idx += 1; + if self.old_idx > self.entities.len() { + self.old_idx -= self.entities.len(); + } + self.entities[self.old_idx - 1] + } +} + +/// Maximum number of entities that can be spawned in the scene view in a single SpawnEvent. +const MAX_SPAWN_PER_EVENT: usize = 20; + +#[derive(Event, Reflect, Clone, Copy)] pub struct SpawnEvent { pub position: Vec2, pub count: f64, } fn spawn_entities( - mut commands: Commands, - mut events: EventReader, - root: Res, - mut simulation: ResMut, - sprite_pack_assets: Res, - mut entity_cap_query: Query<&mut OverflowDespawnQueue, With>, + world: &mut World, + mut pool: Local, + mut reader: Local>, ) { - let mut rng = rand::thread_rng(); - for event in events.read() { - simulation.entities += event.count; + // Fill entity pool initially + let capacity = pool.entities.capacity() - pool.entities.len(); + if capacity > 0 { + pool.entities.extend( + world.spawn_batch( + std::iter::repeat(( + Name::new("Entity"), + // NOTE: Workaround for SpatialBundle not impling Clone + ( + Visibility::Hidden, + InheritedVisibility::default(), + ViewVisibility::default(), + Transform::default(), + GlobalTransform::default(), + ), + WrapWithinSceneView, + Velocity::default(), + TextureAtlasSprite::default(), + Handle::::default(), + )) + .take(capacity), + ), + ); + + let parent = world.resource::().world; + for entity in pool.entities.iter().copied() { + world.entity_mut(entity).set_parent(parent); + } + } + + let mut rng = thread_rng(); + for event in reader + .read(world.resource::>()) + .copied() + .collect::>() + { + world.resource_mut::().entities += event.count; + let simulation = world.resource::(); let spawn_count = MAX_SPAWN_PER_EVENT.min(event.count as usize); + let mut bundles = vec![]; for _ in 0..spawn_count { let angle = rng.gen_range(0.0..=TAU); let direction = Vec2::from_angle(angle); @@ -144,47 +200,36 @@ fn spawn_entities( let offset = rng.gen_range(simulation.spawn_offset_min..=simulation.spawn_offset_max) * direction; let position = (event.position + offset).extend(0.0); + let transform = Transform::from_translation(position); let size = rng.gen_range(simulation.entity_size_min..=simulation.entity_size_max); let size = Vec2::splat(size); - let entity = commands - .spawn(( - Name::new("Entity"), - SpatialBundle::from_transform(Transform::from_translation(position)), - Velocity(velocity), - WrapWithinSceneView, - )) - .set_parent(root.world) - .id(); - simulation.sprite_pack.apply( - &mut commands, - entity, - &sprite_pack_assets, - size, - &mut rng, - ); + let (sprite, texture) = + simulation + .sprite_pack + .bundle(world.resource::(), size, &mut rng); + + bundles.push(( + Visibility::Inherited, + transform, + Velocity(velocity), + sprite, + texture, + )); + } - for mut despawn_queue in &mut entity_cap_query { - despawn_queue.push(entity); - } + for (visibility, transform, velocity, sprite, texture) in bundles { + let mut entity = world.entity_mut(pool.recycle()); + *entity.get_mut::().unwrap() = visibility; + *entity.get_mut::().unwrap() = transform; + *entity.get_mut::().unwrap() = velocity; + *entity.get_mut::().unwrap() = sprite; + *entity.get_mut::>().unwrap() = texture; } } } -#[derive(Component, Reflect)] -struct IsEntityCap; - -const HARD_CAP: usize = 8000; - -fn spawn_entity_caps(mut commands: Commands) { - commands.spawn(( - Name::new("HardEntityCap"), - OverflowDespawnQueue::new(HARD_CAP), - IsEntityCap, - )); -} - /// Resource for handling passive code generation. #[derive(Resource)] pub struct PassiveCodeTyper { diff --git a/src/simulation/sprite_pack.rs b/src/simulation/sprite_pack.rs index 601e486..2ced258 100644 --- a/src/simulation/sprite_pack.rs +++ b/src/simulation/sprite_pack.rs @@ -156,17 +156,16 @@ impl SpritePack { }; } - pub fn apply( + pub fn bundle( &self, - commands: &mut Commands, - entity: Entity, assets: &SpritePackAssets, size: Vec2, mut rng: impl Rng, - ) { - if let Some(&skin) = self.skins.choose(&mut rng) { - commands.entity(entity).insert(skin.bundle(assets, size)); - }; + ) -> (TextureAtlasSprite, Handle) { + self.skins + .choose(&mut rng) + .map(|skin| skin.bundle(assets, size)) + .unwrap_or_default() } pub fn replace_skin_set(&mut self, skin_set: SkinSet, rng: impl Rng) { @@ -205,9 +204,9 @@ fn apply_sprite_pack( Vec2::splat(rng.gen_range(simulation.entity_size_min..=simulation.entity_size_max)) }); - simulation - .sprite_pack - .apply(&mut commands, entity, &assets, size, &mut rng); + commands + .entity(entity) + .insert(simulation.sprite_pack.bundle(&assets, size, &mut rng)); } } diff --git a/src/state/editor_screen/scene_view.rs b/src/state/editor_screen/scene_view.rs index 7caec7a..e691c6f 100644 --- a/src/state/editor_screen/scene_view.rs +++ b/src/state/editor_screen/scene_view.rs @@ -113,7 +113,7 @@ fn update_scene_view_bounds( bounds.max = rect.max.extend(camera_z - proj.near); } -#[derive(Component, Reflect)] +#[derive(Component, Reflect, Clone, Copy)] pub struct WrapWithinSceneView; fn wrap_sprites_within_scene_view( diff --git a/src/util.rs b/src/util.rs index f82265a..d5befd9 100644 --- a/src/util.rs +++ b/src/util.rs @@ -4,7 +4,6 @@ use bevy::prelude::*; use format_num::format_num; pub use crate::util::despawn::DespawnSet; -pub use crate::util::despawn::OverflowDespawnQueue; pub struct UtilPlugin; diff --git a/src/util/despawn.rs b/src/util/despawn.rs index 2a4c7e0..9bf86ff 100644 --- a/src/util/despawn.rs +++ b/src/util/despawn.rs @@ -1,5 +1,3 @@ -use std::collections::VecDeque; - use bevy::prelude::*; use bevy::utils::HashSet; @@ -11,47 +9,7 @@ impl Plugin for DespawnPlugin { fn build(&self, app: &mut App) { app.register_type::() .init_resource::() - .add_systems( - Update, - (apply_overflow_despawn_queue, apply_despawn_set) - .chain() - .in_set(AppSet::Despawn), - ); - } -} - -#[derive(Component, Reflect)] -pub struct OverflowDespawnQueue { - pub entities: VecDeque, - pub cap: usize, -} - -impl OverflowDespawnQueue { - pub fn new(cap: usize) -> Self { - Self { - entities: VecDeque::with_capacity(2 * cap), - cap, - } - } - - pub fn push(&mut self, entity: Entity) { - self.entities.push_back(entity); - } - - pub fn drain_overflow(&mut self) -> impl '_ + Iterator { - let overflow = self.entities.len().saturating_sub(self.cap); - self.entities.drain(0..overflow) - } -} - -fn apply_overflow_despawn_queue( - mut despawn: ResMut, - mut queue_query: Query<&mut OverflowDespawnQueue>, -) { - for mut queue in &mut queue_query { - for entity in queue.drain_overflow() { - despawn.recursive(entity); - } + .add_systems(Update, apply_despawn_set.in_set(AppSet::Despawn)); } }