Skip to main content

rapier_testbed3d_f64/testbed/
state.rs

1//! Testbed state types and flags.
2
3use bitflags::bitflags;
4
5use crate::physics::{PhysicsSnapshot, RapierBroadPhaseType};
6use crate::save::SerializableTestbedState;
7use crate::settings::ExampleSettings;
8
9/// Run mode for the simulation
10#[derive(Default, PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
11pub enum RunMode {
12    Running,
13    #[default]
14    Stop,
15    Step,
16}
17
18/// A loop transition requested from the UI: stop entirely, or switch to another
19/// example (or re-run the current one). The target is carried in
20/// [`TestbedState::selected_display_index`]; this only signals the
21/// example-owned `while viewer.render_frame()` loop to exit so the outer demo
22/// runner can dispatch the next example.
23#[derive(Copy, Clone, PartialEq, Eq, Debug)]
24pub enum Transition {
25    Quit,
26    Switch,
27}
28
29bitflags! {
30    /// Flags for controlling what is displayed in the testbed
31    #[derive(Copy, Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
32    pub struct TestbedStateFlags: u32 {
33        const SLEEP = 1 << 0;
34        const SUB_STEPPING = 1 << 1;
35        const SHAPES = 1 << 2;
36        const JOINTS = 1 << 3;
37        const AABBS = 1 << 4;
38        const CONTACT_POINTS = 1 << 5;
39        const CONTACT_NORMALS = 1 << 6;
40        const CENTER_OF_MASSES = 1 << 7;
41        const WIREFRAME = 1 << 8;
42        const STATISTICS = 1 << 9;
43        const DRAW_SURFACES = 1 << 10;
44    }
45}
46
47impl Default for TestbedStateFlags {
48    fn default() -> Self {
49        TestbedStateFlags::DRAW_SURFACES | TestbedStateFlags::SLEEP
50    }
51}
52
53bitflags! {
54    /// Flags for in-frame testbed actions applied to the borrowed world.
55    ///
56    /// Example switching / restart / backend changes are no longer flags — they
57    /// are handled by [`Transition`], which makes the example's render loop exit
58    /// so the outer demo runner re-dispatches.
59    #[derive(Copy, Clone, PartialEq, Eq, Debug)]
60    pub struct TestbedActionFlags: u32 {
61        const RESET_WORLD_GRAPHICS = 1 << 0;
62        const TAKE_SNAPSHOT = 1 << 4;
63        const RESTORE_SNAPSHOT = 1 << 5;
64        const APP_STARTED = 1 << 6;
65        /// Recenter the camera so the entire scene is visible and fills
66        /// the viewport.
67        const FRAME_SCENE = 1 << 7;
68    }
69}
70
71/// Which tab is currently selected in the UI
72#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)]
73pub enum UiTab {
74    #[default]
75    Examples,
76    Settings,
77    Performance,
78}
79
80/// Information about an example for UI display.
81#[derive(Clone, Debug)]
82pub struct ExampleEntry {
83    pub name: &'static str,
84    pub group: &'static str,
85}
86
87impl ExampleEntry {
88    pub fn new(group: &'static str, name: &'static str) -> Self {
89        Self { name, group }
90    }
91}
92
93/// State for the testbed application
94pub struct TestbedState {
95    pub running: RunMode,
96    pub can_grab_behind_ground: bool,
97    pub prev_flags: TestbedStateFlags,
98    pub flags: TestbedStateFlags,
99    pub action_flags: TestbedActionFlags,
100    /// Pending loop transition (example switch / quit) requested from the UI.
101    pub transition: Option<Transition>,
102    /// `true` while a restart / solver-parameter change is the reason for a
103    /// pending [`Transition::Switch`]; such switches preserve the user's
104    /// example-setting edits, whereas selecting a different example clears them.
105    pub preserve_settings_on_switch: bool,
106    /// Examples in display order (grouped, then by original order within group)
107    pub examples: Vec<ExampleEntry>,
108    /// Unique group names in order of first appearance
109    pub example_groups: Vec<&'static str>,
110    /// Currently selected position in the display order
111    pub selected_display_index: usize,
112    pub example_settings: ExampleSettings,
113    pub broad_phase_type: RapierBroadPhaseType,
114    pub snapshot: Option<PhysicsSnapshot>,
115    /// Number of physics steps run since the example was (re)started. Bumped by
116    /// [`crate::TestbedViewer::simulating`], and carried through snapshot
117    /// save/restore so a restored world reports the step it was saved at.
118    pub timestep_id: usize,
119    pub camera_locked: bool,
120    pub selected_tab: UiTab,
121    pub prev_save_data: SerializableTestbedState,
122    /// Unit up-vector kept in sync with the camera (see
123    /// [`crate::TestbedViewer::set_up_axis`]). The gravity slider in the
124    /// testbed UI reads this so it can keep gravity aligned with "down"
125    /// (`-up_axis`) instead of the hard-coded Y-axis it used to assume.
126    /// Defaults to `Vector::Y`.
127    pub up_axis: rapier::math::Vector,
128    /// Thread pool running the physics step, shared across example reloads: sized
129    /// to the performance cores (efficiency cores stall the solver's
130    /// barrier-paced parallel stages). Built lazily on the first `set_world`.
131    #[cfg(feature = "parallel")]
132    pub physics_thread_pool: Option<std::sync::Arc<rapier::rayon::ThreadPool>>,
133}
134
135impl Default for TestbedState {
136    fn default() -> Self {
137        let flags = TestbedStateFlags::default();
138        Self {
139            running: RunMode::Running,
140            can_grab_behind_ground: false,
141            snapshot: None,
142            timestep_id: 0,
143            prev_flags: flags,
144            flags,
145            action_flags: TestbedActionFlags::APP_STARTED,
146            transition: None,
147            preserve_settings_on_switch: false,
148            examples: Vec::new(),
149            example_groups: Vec::new(),
150            example_settings: ExampleSettings::default(),
151            selected_display_index: 0,
152            broad_phase_type: RapierBroadPhaseType::default(),
153            camera_locked: false,
154            selected_tab: UiTab::default(),
155            prev_save_data: SerializableTestbedState::default(),
156            up_axis: rapier::math::Vector::Y,
157            #[cfg(feature = "parallel")]
158            physics_thread_pool: None,
159        }
160    }
161}
162
163impl TestbedState {
164    /// Builds the grouped display order from a flat list of examples.
165    pub fn set_examples(&mut self, examples: Vec<ExampleEntry>) {
166        use indexmap::IndexSet;
167
168        let mut groups: IndexSet<&'static str> = IndexSet::new();
169        for example in &examples {
170            groups.insert(example.group);
171        }
172
173        let mut ordered = Vec::new();
174        for group in &groups {
175            for example in &examples {
176                if example.group == *group {
177                    ordered.push(example.clone());
178                }
179            }
180        }
181
182        self.example_groups = groups.into_iter().collect();
183        self.examples = ordered;
184    }
185}