Change check_visibility to use thread-local queues instead of a channel (#4663)

# Objective
Further speed up visibility checking by removing the main sources of contention for the system.

## Solution
 - ~~Make `ComputedVisibility` a resource wrapping a `FixedBitset`.~~
 - ~~Remove `ComputedVisibility` as a component.~~

~~This adds a one-bit overhead to every entity in the app world. For a game with 100,000 entities, this is 12.5KB of memory. This is still small enough to fit entirely in most L1 caches. Also removes the need for a per-Entity change detection tick. This reduces the memory footprint of ComputedVisibility 72x.~~

~~The decreased memory usage and less fragmented memory locality should provide significant performance benefits.~~

~~Clearing visible entities should be significantly faster than before:~~
 
 - ~~Setting one `u32` to 0 clears 32 entities per cycle.~~
 - ~~No archetype fragmentation to contend with.~~
 - ~~Change detection is applied to the resource, so there is no per-Entity update tick requirement.~~

~~The side benefit of this design is that it removes one more "computed component" from userspace.  Though accessing the values within it are now less ergonomic.~~

This PR changes `crossbeam_channel` in `check_visibility` to use a `Local<ThreadLocal<Cell<Vec<Entity>>>` to mark down visible entities instead.

Co-Authored-By: TheRawMeatball <therawmeatball@gmail.com>
Co-Authored-By: Aevyrie <aevyrie@gmail.com>
This commit is contained in:
James Liu 2022-06-21 18:10:27 +00:00
parent 511bcc9633
commit 389df18343
2 changed files with 31 additions and 17 deletions

View file

@ -54,9 +54,9 @@ bitflags = "1.2.1"
smallvec = { version = "1.6", features = ["union", "const_generics"] } smallvec = { version = "1.6", features = ["union", "const_generics"] }
once_cell = "1.4.1" # TODO: replace once_cell with std equivalent if/when this lands: https://github.com/rust-lang/rfcs/pull/2788 once_cell = "1.4.1" # TODO: replace once_cell with std equivalent if/when this lands: https://github.com/rust-lang/rfcs/pull/2788
downcast-rs = "1.2.0" downcast-rs = "1.2.0"
thread_local = "1.1"
thiserror = "1.0" thiserror = "1.0"
futures-lite = "1.4.0" futures-lite = "1.4.0"
crossbeam-channel = "0.5.0"
anyhow = "1.0" anyhow = "1.0"
hex = "0.4.2" hex = "0.4.2"
hexasphere = "7.0.0" hexasphere = "7.0.0"

View file

@ -10,6 +10,8 @@ use bevy_reflect::std_traits::ReflectDefault;
use bevy_reflect::Reflect; use bevy_reflect::Reflect;
use bevy_transform::components::GlobalTransform; use bevy_transform::components::GlobalTransform;
use bevy_transform::TransformSystem; use bevy_transform::TransformSystem;
use std::cell::Cell;
use thread_local::ThreadLocal;
use crate::{ use crate::{
camera::{Camera, CameraProjection, OrthographicProjection, PerspectiveProjection, Projection}, camera::{Camera, CameraProjection, OrthographicProjection, PerspectiveProjection, Projection},
@ -148,22 +150,30 @@ pub fn update_frusta<T: Component + CameraProjection + Send + Sync + 'static>(
} }
pub fn check_visibility( pub fn check_visibility(
mut thread_queues: Local<ThreadLocal<Cell<Vec<Entity>>>>,
mut view_query: Query<(&mut VisibleEntities, &Frustum, Option<&RenderLayers>), With<Camera>>, mut view_query: Query<(&mut VisibleEntities, &Frustum, Option<&RenderLayers>), With<Camera>>,
mut visible_entity_query: Query<( mut visible_entity_query: ParamSet<(
Entity, Query<&mut ComputedVisibility>,
&Visibility, Query<(
&mut ComputedVisibility, Entity,
Option<&RenderLayers>, &Visibility,
Option<&Aabb>, &mut ComputedVisibility,
Option<&NoFrustumCulling>, Option<&RenderLayers>,
Option<&GlobalTransform>, Option<&Aabb>,
Option<&NoFrustumCulling>,
Option<&GlobalTransform>,
)>,
)>, )>,
) { ) {
// Reset the computed visibility to false
for mut computed_visibility in visible_entity_query.p0().iter_mut() {
computed_visibility.is_visible = false;
}
for (mut visible_entities, frustum, maybe_view_mask) in view_query.iter_mut() { for (mut visible_entities, frustum, maybe_view_mask) in view_query.iter_mut() {
let view_mask = maybe_view_mask.copied().unwrap_or_default(); let view_mask = maybe_view_mask.copied().unwrap_or_default();
let (visible_entity_sender, visible_entity_receiver) = crossbeam_channel::unbounded(); visible_entities.entities.clear();
visible_entity_query.p1().par_for_each_mut(
visible_entity_query.par_for_each_mut(
1024, 1024,
|( |(
entity, entity,
@ -174,12 +184,10 @@ pub fn check_visibility(
maybe_no_frustum_culling, maybe_no_frustum_culling,
maybe_transform, maybe_transform,
)| { )| {
// Reset visibility
computed_visibility.is_visible = false;
if !visibility.is_visible { if !visibility.is_visible {
return; return;
} }
let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); let entity_mask = maybe_entity_mask.copied().unwrap_or_default();
if !view_mask.intersects(&entity_mask) { if !view_mask.intersects(&entity_mask) {
return; return;
@ -205,9 +213,15 @@ pub fn check_visibility(
} }
computed_visibility.is_visible = true; computed_visibility.is_visible = true;
visible_entity_sender.send(entity).ok(); let cell = thread_queues.get_or_default();
let mut queue = cell.take();
queue.push(entity);
cell.set(queue);
}, },
); );
visible_entities.entities = visible_entity_receiver.try_iter().collect();
for cell in thread_queues.iter_mut() {
visible_entities.entities.append(cell.get_mut());
}
} }
} }