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

[Merged by Bors] - Optimize rendering slow-down at high entity counts #5509

1 change: 1 addition & 0 deletions crates/bevy_ecs/src/archetype.rs
Expand Up @@ -14,6 +14,7 @@ use std::{
};

#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[repr(transparent)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably makes sense to add a comment to ArchetypeId::INVALID saying that it must be equivalent to setting all bytes of memory to u8::MAX (and referencing flush_and_reserve_invalid). Don't want anyone changing this here and then implicitly breaking flush_and_reserve_invalid.

pub struct ArchetypeId(usize);

impl ArchetypeId {
Expand Down
21 changes: 21 additions & 0 deletions crates/bevy_ecs/src/entity/mod.rs
Expand Up @@ -550,6 +550,9 @@ impl Entities {
/// Flush _must_ set the entity location to the correct [`ArchetypeId`] for the given [`Entity`]
/// each time init is called. This _can_ be [`ArchetypeId::INVALID`], provided the [`Entity`]
/// has not been assigned to an [`Archetype`][crate::archetype::Archetype].
///
/// Note: freshly-allocated entities (ones which don't come from the pending list) are guaranteed
/// to be initialized with the invalid archetype.
pub unsafe fn flush(&mut self, mut init: impl FnMut(Entity, &mut EntityLocation)) {
let free_cursor = self.free_cursor.get_mut();
let current_free_cursor = *free_cursor;
Expand Down Expand Up @@ -600,6 +603,20 @@ impl Entities {
}
}

/// # Safety
///
/// This function is safe if and only if the world this Entities is on has no entities.
pub unsafe fn flush_and_reserve_invalid_assuming_no_entities(&mut self, count: usize) {
let free_cursor = self.free_cursor.get_mut();
*free_cursor = 0;
self.meta.reserve(count);
// the EntityMeta struct only contains integers, and it is valid to have all bytes set to u8::MAX
self.meta.as_mut_ptr().write_bytes(u8::MAX, count);
self.meta.set_len(count);

self.len = count as u32;
}

/// Accessor for getting the length of the vec in `self.meta`
#[inline]
pub fn meta_len(&self) -> usize {
Expand All @@ -617,7 +634,10 @@ impl Entities {
}
}

// Safety:
// This type must not contain any pointers at any level, and be safe to fully fill with u8::MAX.
#[derive(Copy, Clone, Debug)]
#[repr(C)]
pub struct EntityMeta {
pub generation: u32,
pub location: EntityLocation,
Expand All @@ -635,6 +655,7 @@ impl EntityMeta {

/// A location of an entity in an archetype.
#[derive(Copy, Clone, Debug)]
#[repr(C)]
pub struct EntityLocation {
/// The archetype index
pub archetype_id: ArchetypeId,
Expand Down
23 changes: 14 additions & 9 deletions crates/bevy_render/src/lib.rs
Expand Up @@ -218,15 +218,20 @@ impl Plugin for RenderPlugin {
// reserve all existing app entities for use in render_app
// they can only be spawned using `get_or_spawn()`
let meta_len = app_world.entities().meta_len();
render_app
.world
.entities()
.reserve_entities(meta_len as u32);

// flushing as "invalid" ensures that app world entities aren't added as "empty archetype" entities by default
// these entities cannot be accessed without spawning directly onto them
// this _only_ works as expected because clear_entities() is called at the end of every frame.
unsafe { render_app.world.entities_mut() }.flush_as_invalid();

assert_eq!(
render_app.world.entities().len(),
0,
"An entity was spawned after the entity list was cleared last frame and before the extract stage began. This is not supported",
);

// This is safe given the clear_entities call in the past frame and the assert above
unsafe {
render_app
.world
.entities_mut()
.flush_and_reserve_invalid_assuming_no_entities(meta_len);
}
}

{
Expand Down
38 changes: 33 additions & 5 deletions examples/hello_world.rs
@@ -1,9 +1,37 @@
use bevy::prelude::*;
TheRawMeatball marked this conversation as resolved.
Show resolved Hide resolved
use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
prelude::*,
};

fn main() {
App::new().add_system(hello_world_system).run();
#[derive(Component)]
pub struct Nothing;

#[derive(Bundle)]
pub struct NoBundle {
nothing: Nothing,
}

fn startup(mut commands: Commands) {
let mut entities = Vec::new();
for _ in 0..40_000_000 {
entities.push(NoBundle { nothing: Nothing });
}

commands.spawn_batch(entities);
}

fn hello_world_system() {
println!("hello world");
fn main() {
App::new()
.insert_resource(WindowDescriptor {
width: 1270.0,
height: 720.0,
title: String::from("Bug"),
..Default::default()
})
.insert_resource(ClearColor(Color::rgb(0.211, 0.643, 0.949)))
.add_plugin(FrameTimeDiagnosticsPlugin::default())
.add_plugin(LogDiagnosticsPlugin::default())
.add_plugins(DefaultPlugins)
.add_startup_system(startup)
.run();
}