avian3d/collider_tree/
optimization.rs1use crate::{
2 collider_tree::{
3 ColliderTree, ColliderTreeDiagnostics, ColliderTreeSystems, ColliderTreeType, ColliderTrees,
4 },
5 data_structures::stable_vec::StableVec,
6 prelude::*,
7};
8use bevy::{
9 ecs::world::CommandQueue,
10 prelude::*,
11 tasks::{AsyncComputeTaskPool, Task, block_on},
12};
13
14pub(super) struct ColliderTreeOptimizationPlugin;
16
17impl Plugin for ColliderTreeOptimizationPlugin {
18 fn build(&self, app: &mut App) {
19 app.init_resource::<ColliderTreeOptimization>()
20 .init_resource::<OptimizationTasks>();
21
22 app.add_systems(
23 PhysicsSchedule,
24 (
25 optimize_trees.in_set(ColliderTreeSystems::BeginOptimize),
26 #[cfg(all(not(target_arch = "wasm32"), not(target_os = "unknown")))]
27 block_on_optimize_trees.in_set(ColliderTreeSystems::EndOptimize),
28 ),
29 );
30 }
31}
32
33#[derive(Resource, Debug, PartialEq, Reflect)]
36pub struct ColliderTreeOptimization {
37 pub optimization_mode: TreeOptimizationMode,
41
42 pub optimize_in_place: bool,
58
59 pub use_async_tasks: bool,
64}
65
66impl Default for ColliderTreeOptimization {
67 fn default() -> Self {
68 Self {
69 optimization_mode: TreeOptimizationMode::default(),
70 optimize_in_place: false,
71 #[cfg(any(target_arch = "wasm32", target_os = "unknown"))]
72 use_async_tasks: false,
73 #[cfg(all(not(target_arch = "wasm32"), not(target_os = "unknown")))]
74 use_async_tasks: true,
75 }
76 }
77}
78
79#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
81pub enum TreeOptimizationMode {
82 Reinsert,
87
88 PartialRebuild,
95
96 FullRebuild,
102
103 Adaptive {
113 reinsert_threshold: f32,
118
119 partial_rebuild_threshold: f32,
124 },
125}
126
127impl Default for TreeOptimizationMode {
128 fn default() -> Self {
129 TreeOptimizationMode::Adaptive {
130 reinsert_threshold: 0.15,
131 partial_rebuild_threshold: 0.45,
132 }
133 }
134}
135
136impl TreeOptimizationMode {
137 #[inline]
141 pub fn resolve(&self, moved_ratio: f32) -> TreeOptimizationMode {
142 match self {
143 TreeOptimizationMode::Adaptive {
144 reinsert_threshold,
145 partial_rebuild_threshold,
146 } => {
147 if moved_ratio < *reinsert_threshold {
148 TreeOptimizationMode::Reinsert
149 } else if moved_ratio < *partial_rebuild_threshold {
150 TreeOptimizationMode::PartialRebuild
151 } else {
152 TreeOptimizationMode::FullRebuild
153 }
154 }
155 other => *other,
156 }
157 }
158}
159
160#[derive(Resource, Default, Deref, DerefMut)]
162struct OptimizationTasks(Vec<Task<CommandQueue>>);
163
164fn optimize_trees(
170 mut collider_trees: ResMut<ColliderTrees>,
171 mut optimization_tasks: ResMut<OptimizationTasks>,
172 optimization_settings: Res<ColliderTreeOptimization>,
173 mut diagnostics: ResMut<ColliderTreeDiagnostics>,
174) {
175 let start = crate::utils::Instant::now();
176
177 let task_pool = AsyncComputeTaskPool::get();
178
179 #[cfg(any(target_arch = "wasm32", target_os = "unknown"))]
181 let use_async_tasks = false;
182 #[cfg(all(not(target_arch = "wasm32"), not(target_os = "unknown")))]
183 let use_async_tasks = optimization_settings.use_async_tasks;
184
185 for tree_type in ColliderTreeType::ALL {
187 let tree = collider_trees.tree_for_type_mut(tree_type);
188
189 let moved_ratio = tree.moved_proxies.len() as f32 / tree.proxies.len() as f32;
190 let optimization_strategy = optimization_settings.optimization_mode.resolve(moved_ratio);
191
192 if moved_ratio == 0.0 && optimization_strategy != TreeOptimizationMode::FullRebuild {
193 continue;
195 }
196
197 #[cfg(all(not(target_arch = "wasm32"), not(target_os = "unknown")))]
198 if use_async_tasks {
199 let bvh = if optimization_settings.optimize_in_place {
203 core::mem::take(&mut tree.bvh)
204 } else {
205 tree.bvh.clone()
207 };
208
209 let new_tree = ColliderTree {
211 bvh,
212 proxies: StableVec::new(),
213 moved_proxies: core::mem::take(&mut tree.moved_proxies),
215 workspace: core::mem::take(&mut tree.workspace),
216 };
217
218 let task = spawn_optimization_task(task_pool, new_tree, tree_type, move |tree| {
219 optimize_tree_in_place(tree, optimization_strategy);
220 });
221
222 optimization_tasks.push(task);
223 }
224
225 if !use_async_tasks {
226 optimize_tree_in_place(tree, optimization_strategy);
228 }
229 }
230
231 diagnostics.optimize += start.elapsed();
232}
233
234fn optimize_tree_in_place(tree: &mut ColliderTree, optimization_strategy: TreeOptimizationMode) {
235 match optimization_strategy {
236 TreeOptimizationMode::Reinsert => {
237 let moved_leaves = tree
238 .moved_proxies
239 .iter()
240 .map(|key| tree.bvh.primitives_to_nodes[key.index()])
241 .collect::<Vec<u32>>();
242
243 tree.optimize_candidates(&moved_leaves, 1);
244 }
245 TreeOptimizationMode::PartialRebuild => {
246 let moved_leaves = tree
247 .moved_proxies
248 .iter()
249 .map(|key| tree.bvh.primitives_to_nodes[key.index()])
250 .collect::<Vec<u32>>();
251
252 tree.rebuild_partial(&moved_leaves);
253 }
254 TreeOptimizationMode::FullRebuild => {
255 tree.rebuild_full();
256 }
257
258 TreeOptimizationMode::Adaptive { .. } => unreachable!(),
259 }
260}
261
262#[cfg(all(not(target_arch = "wasm32"), not(target_os = "unknown")))]
265fn spawn_optimization_task(
266 task_pool: &AsyncComputeTaskPool,
267 mut tree: ColliderTree,
268 tree_type: ColliderTreeType,
269 optimize: impl FnOnce(&mut ColliderTree) + Send + 'static,
270) -> Task<CommandQueue> {
271 task_pool.spawn(async move {
272 optimize(&mut tree);
273
274 let mut command_queue = CommandQueue::default();
275 command_queue.push(move |world: &mut World| {
276 let mut collider_trees = world
277 .get_resource_mut::<ColliderTrees>()
278 .expect("ColliderTrees resource missing");
279 let collider_tree = collider_trees.tree_for_type_mut(tree_type);
280 collider_tree.bvh = tree.bvh;
281 collider_tree.workspace = tree.workspace;
282 });
283 command_queue
284 })
285}
286
287#[cfg(all(not(target_arch = "wasm32"), not(target_os = "unknown")))]
289fn block_on_optimize_trees(
290 mut commands: Commands,
291 mut optimization: ResMut<OptimizationTasks>,
292 mut diagnostics: ResMut<ColliderTreeDiagnostics>,
293) {
294 let start = crate::utils::Instant::now();
295
296 optimization.drain(..).for_each(|task| {
298 let mut command_queue = block_on(task);
299 commands.append(&mut command_queue);
300 });
301
302 diagnostics.optimize += start.elapsed();
303}