use crate::{
collider_tree::{
ColliderTree, ColliderTreeDiagnostics, ColliderTreeSystems, ColliderTreeType, ColliderTrees,
},
data_structures::stable_vec::StableVec,
prelude::*,
};
use bevy::{
ecs::world::CommandQueue,
prelude::*,
tasks::{AsyncComputeTaskPool, Task, block_on},
};
pub(super) struct ColliderTreeOptimizationPlugin;
impl Plugin for ColliderTreeOptimizationPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<ColliderTreeOptimization>()
.init_resource::<OptimizationTasks>();
app.add_systems(
PhysicsSchedule,
(
optimize_trees.in_set(ColliderTreeSystems::BeginOptimize),
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "unknown")))]
block_on_optimize_trees.in_set(ColliderTreeSystems::EndOptimize),
),
);
}
}
#[derive(Resource, Debug, PartialEq, Reflect)]
pub struct ColliderTreeOptimization {
pub optimization_mode: TreeOptimizationMode,
pub optimize_in_place: bool,
pub use_async_tasks: bool,
}
impl Default for ColliderTreeOptimization {
fn default() -> Self {
Self {
optimization_mode: TreeOptimizationMode::default(),
optimize_in_place: false,
#[cfg(any(target_arch = "wasm32", target_os = "unknown"))]
use_async_tasks: false,
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "unknown")))]
use_async_tasks: true,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
pub enum TreeOptimizationMode {
Reinsert,
PartialRebuild,
FullRebuild,
Adaptive {
reinsert_threshold: f32,
partial_rebuild_threshold: f32,
},
}
impl Default for TreeOptimizationMode {
fn default() -> Self {
TreeOptimizationMode::Adaptive {
reinsert_threshold: 0.15,
partial_rebuild_threshold: 0.45,
}
}
}
impl TreeOptimizationMode {
#[inline]
pub fn resolve(&self, moved_ratio: f32) -> TreeOptimizationMode {
match self {
TreeOptimizationMode::Adaptive {
reinsert_threshold,
partial_rebuild_threshold,
} => {
if moved_ratio < *reinsert_threshold {
TreeOptimizationMode::Reinsert
} else if moved_ratio < *partial_rebuild_threshold {
TreeOptimizationMode::PartialRebuild
} else {
TreeOptimizationMode::FullRebuild
}
}
other => *other,
}
}
}
#[derive(Resource, Default, Deref, DerefMut)]
struct OptimizationTasks(Vec<Task<CommandQueue>>);
fn optimize_trees(
mut collider_trees: ResMut<ColliderTrees>,
mut optimization_tasks: ResMut<OptimizationTasks>,
optimization_settings: Res<ColliderTreeOptimization>,
mut diagnostics: ResMut<ColliderTreeDiagnostics>,
) {
let start = crate::utils::Instant::now();
let task_pool = AsyncComputeTaskPool::get();
#[cfg(any(target_arch = "wasm32", target_os = "unknown"))]
let use_async_tasks = false;
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "unknown")))]
let use_async_tasks = optimization_settings.use_async_tasks;
for tree_type in ColliderTreeType::ALL {
let tree = collider_trees.tree_for_type_mut(tree_type);
let moved_ratio = tree.moved_proxies.len() as f32 / tree.proxies.len() as f32;
let optimization_strategy = optimization_settings.optimization_mode.resolve(moved_ratio);
if moved_ratio == 0.0 && optimization_strategy != TreeOptimizationMode::FullRebuild {
continue;
}
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "unknown")))]
if use_async_tasks {
let bvh = if optimization_settings.optimize_in_place {
core::mem::take(&mut tree.bvh)
} else {
tree.bvh.clone()
};
let new_tree = ColliderTree {
bvh,
proxies: StableVec::new(),
moved_proxies: core::mem::take(&mut tree.moved_proxies),
workspace: core::mem::take(&mut tree.workspace),
};
let task = spawn_optimization_task(task_pool, new_tree, tree_type, move |tree| {
optimize_tree_in_place(tree, optimization_strategy);
});
optimization_tasks.push(task);
}
if !use_async_tasks {
optimize_tree_in_place(tree, optimization_strategy);
}
}
diagnostics.optimize += start.elapsed();
}
fn optimize_tree_in_place(tree: &mut ColliderTree, optimization_strategy: TreeOptimizationMode) {
match optimization_strategy {
TreeOptimizationMode::Reinsert => {
let moved_leaves = tree
.moved_proxies
.iter()
.map(|key| tree.bvh.primitives_to_nodes[key.index()])
.collect::<Vec<u32>>();
tree.optimize_candidates(&moved_leaves, 1);
}
TreeOptimizationMode::PartialRebuild => {
let moved_leaves = tree
.moved_proxies
.iter()
.map(|key| tree.bvh.primitives_to_nodes[key.index()])
.collect::<Vec<u32>>();
tree.rebuild_partial(&moved_leaves);
}
TreeOptimizationMode::FullRebuild => {
tree.rebuild_full();
}
TreeOptimizationMode::Adaptive { .. } => unreachable!(),
}
}
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "unknown")))]
fn spawn_optimization_task(
task_pool: &AsyncComputeTaskPool,
mut tree: ColliderTree,
tree_type: ColliderTreeType,
optimize: impl FnOnce(&mut ColliderTree) + Send + 'static,
) -> Task<CommandQueue> {
task_pool.spawn(async move {
optimize(&mut tree);
let mut command_queue = CommandQueue::default();
command_queue.push(move |world: &mut World| {
let mut collider_trees = world
.get_resource_mut::<ColliderTrees>()
.expect("ColliderTrees resource missing");
let collider_tree = collider_trees.tree_for_type_mut(tree_type);
collider_tree.bvh = tree.bvh;
collider_tree.workspace = tree.workspace;
});
command_queue
})
}
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "unknown")))]
fn block_on_optimize_trees(
mut commands: Commands,
mut optimization: ResMut<OptimizationTasks>,
mut diagnostics: ResMut<ColliderTreeDiagnostics>,
) {
let start = crate::utils::Instant::now();
optimization.drain(..).for_each(|task| {
let mut command_queue = block_on(task);
commands.append(&mut command_queue);
});
diagnostics.optimize += start.elapsed();
}