Skip to main content

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}