khora_data/ecs/systems/physics_debug_extraction.rs
1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Physics debug-render extraction — opt-in DataSystem.
16//!
17//! Lifts the previous `PhysicsDebugLane` (which was registered as a second
18//! lane on `PhysicsAgent` and *replaced* the simulation when active — i.e.
19//! enabling debug overlay also stopped physics) into a side-channel
20//! [`crate::ecs::DataSystem`] that runs in `PostSimulation` *alongside* the
21//! normal physics step. Per CLAD: debug data extraction is a Data-domain
22//! invariant projection, not an alternative agent strategy.
23//!
24//! Activation is per-entity via [`PhysicsDebugData::enabled`]: when no
25//! entity has the component, the system is a no-op.
26
27use std::sync::{Arc, Mutex};
28
29use khora_core::lane::OutputDeck;
30use khora_core::physics::PhysicsProvider;
31use khora_core::Runtime;
32
33use crate::ecs::{DataSystemRegistration, PhysicsDebugData, TickPhase, World};
34
35fn physics_debug_extraction(world: &mut World, runtime: &Runtime, _deck: &mut OutputDeck) {
36 let Some(provider_arc) = runtime
37 .backends
38 .get::<Arc<Mutex<Box<dyn PhysicsProvider>>>>()
39 else {
40 return;
41 };
42
43 // Cheap pre-check: skip the lock entirely when no entity carries
44 // `PhysicsDebugData`. Avoids contending the physics provider mutex on
45 // every frame in shipping builds.
46 if world.query::<&PhysicsDebugData>().next().is_none() {
47 return;
48 }
49
50 let guard = match provider_arc.lock() {
51 Ok(g) => g,
52 Err(e) => {
53 log::error!("physics_debug_extraction: provider mutex poisoned: {}", e);
54 return;
55 }
56 };
57
58 for debug_data in world.query_mut::<&mut PhysicsDebugData>() {
59 if debug_data.enabled {
60 let (vertices, indices) = guard.get_debug_render_data();
61 debug_data.vertices = vertices;
62 debug_data.indices = indices;
63 } else {
64 debug_data.vertices.clear();
65 debug_data.indices.clear();
66 }
67 }
68}
69
70inventory::submit! {
71 DataSystemRegistration {
72 name: "physics_debug_extraction",
73 phase: TickPhase::PostSimulation,
74 run: physics_debug_extraction,
75 order_hint: 100, // After transform_propagation (which runs at default 0).
76 runs_after: &["transform_propagation"],
77 }
78}