Compare commits

...
Author SHA1 Message Date
EIREXE c849caf5b9 Try and fix CI
CI / build-and-test (pull_request) Successful in 24m33s
2026-09-14 21:34:35 +02:00
EIREXE 9f66d55dcc Add keyboard support and hold to step
CI / build-and-test (pull_request) Failing after 15m7s
2026-09-14 19:12:25 +02:00
EIREXE 578648d2ec Add debug frame stepping
CI / build-and-test (pull_request) Failing after 15m6s
2026-09-14 18:47:55 +02:00
2 changed files with 99 additions and 4 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ on:
jobs: jobs:
build-and-test: build-and-test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15 timeout-minutes: 30
steps: steps:
- name: Check out source - name: Check out source
uses: actions/checkout@v4 uses: actions/checkout@v4
+96 -1
View File
@@ -3,11 +3,12 @@
use std::{collections::HashMap, time::Duration}; use std::{collections::HashMap, time::Duration};
use bevy::{ use bevy::{
input::InputSystems,
prelude::*, prelude::*,
settings::{ReflectSettingsGroup, SaveSettingsDeferred, SettingsGroup}, settings::{ReflectSettingsGroup, SaveSettingsDeferred, SettingsGroup},
}; };
use bevy_egui::{EguiContexts, EguiPrimaryContextPass, egui}; use bevy_egui::{EguiContexts, EguiPrimaryContextPass, egui};
use bevy_ggrs::RollbackFrameRate; use bevy_ggrs::{RollbackFrameRate, RunGgrsSystems};
use enum_cycling::EnumCycle; use enum_cycling::EnumCycle;
use enum_cycling_derive::EnumCycle; use enum_cycling_derive::EnumCycle;
@@ -114,6 +115,7 @@ enum DebugFlag {
Hurtboxes, Hurtboxes,
Camera, Camera,
DrawThroughGeometry, DrawThroughGeometry,
FrameStepping,
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@@ -174,6 +176,10 @@ const WORLD_MENU: &[DebugMenuEntry] = &[
DebugMenuEntry::GameSpeed { DebugMenuEntry::GameSpeed {
label: "Game speed", label: "Game speed",
}, },
DebugMenuEntry::Toggle {
label: "Frame stepping",
flag: DebugFlag::FrameStepping,
},
]; ];
const DIAGNOSTICS_MENU: &[DebugMenuEntry] = &[DebugMenuEntry::Toggle { const DIAGNOSTICS_MENU: &[DebugMenuEntry] = &[DebugMenuEntry::Toggle {
@@ -233,6 +239,8 @@ pub struct DebugSettings {
selections: Vec<usize>, selections: Vec<usize>,
#[reflect(ignore)] #[reflect(ignore)]
pub player_rows: HashMap<usize, [FighterDebugRowKind; PLAYER_DEBUG_ROW_COUNT]>, pub player_rows: HashMap<usize, [FighterDebugRowKind; PLAYER_DEBUG_ROW_COUNT]>,
#[reflect(ignore)]
pub frame_stepping: bool,
pub network: bool, pub network: bool,
pub ecb: bool, pub ecb: bool,
pub stage_collision: bool, pub stage_collision: bool,
@@ -253,6 +261,7 @@ impl Default for DebugSettings {
pages: vec![DebugMenuPage::Static(ROOT_MENU)], pages: vec![DebugMenuPage::Static(ROOT_MENU)],
selections: vec![0], selections: vec![0],
player_rows: HashMap::new(), player_rows: HashMap::new(),
frame_stepping: false,
network: false, network: false,
ecb: false, ecb: false,
stage_collision: false, stage_collision: false,
@@ -325,6 +334,7 @@ impl DebugSettings {
DebugFlag::Hurtboxes => self.hurtboxes, DebugFlag::Hurtboxes => self.hurtboxes,
DebugFlag::DrawThroughGeometry => self.draw_through_geometry, DebugFlag::DrawThroughGeometry => self.draw_through_geometry,
DebugFlag::Camera => self.camera, DebugFlag::Camera => self.camera,
DebugFlag::FrameStepping => self.frame_stepping,
} }
} }
@@ -340,6 +350,7 @@ impl DebugSettings {
self.draw_through_geometry = !self.draw_through_geometry self.draw_through_geometry = !self.draw_through_geometry
} }
DebugFlag::Camera => self.camera = !self.camera, DebugFlag::Camera => self.camera = !self.camera,
DebugFlag::FrameStepping => self.frame_stepping = !self.frame_stepping,
} }
} }
@@ -356,9 +367,25 @@ impl DebugSettings {
pub struct DebugToolsPlugin; pub struct DebugToolsPlugin;
#[derive(Resource, Default)]
struct FrameStepState {
requested: bool,
time_backup: Option<Time>,
last_step_time: Time
}
impl Plugin for DebugToolsPlugin { impl Plugin for DebugToolsPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.init_resource::<DebugSettings>() app.init_resource::<DebugSettings>()
.init_resource::<FrameStepState>()
.add_systems(
PreUpdate,
prepare_frame_step
.after(InputSystems)
.before(RunGgrsSystems)
.run_if(in_state(AppState::InMatch)),
)
.add_systems(PreUpdate, restore_frame_step_time.after(RunGgrsSystems))
.add_systems( .add_systems(
Update, Update,
(handle_palette_input, sync_gizmo_depth_bias) (handle_palette_input, sync_gizmo_depth_bias)
@@ -391,6 +418,61 @@ fn queue_settings_save(commands: &mut Commands) {
commands.queue(SaveSettingsDeferred(Duration::from_secs_f32(0.1))); commands.queue(SaveSettingsDeferred(Duration::from_secs_f32(0.1)));
} }
fn frame_step_duration(frame_rate: usize) -> Duration {
Duration::from_nanos(1_000_000_000u64 / frame_rate as u64)
}
fn prepare_frame_step(
settings: Res<DebugSettings>,
gamepads: Query<&Gamepad>,
key: Res<ButtonInput<KeyCode>>,
mut state: ResMut<FrameStepState>,
mut time: ResMut<Time>,
frame_rate: Res<RollbackFrameRate>,
) {
if !settings.frame_stepping {
state.requested = false;
return;
}
let pressed = gamepads
.iter()
.any(|gamepad| gamepad.pressed(GamepadButton::DPadDown));
let just_pressed = gamepads
.iter()
.any(|gamepad| gamepad.just_pressed(GamepadButton::DPadDown));
let just_pressed = key.just_pressed(KeyCode::F4) || just_pressed;
let pressed = key.pressed(KeyCode::F4) || pressed;
if just_pressed {
state.last_step_time = time.clone();
}
let can_repress = pressed && (time.elapsed() - state.last_step_time.elapsed()).as_millis() > 500;
if !settings.menu_open && (just_pressed || can_repress)
{
state.requested = true;
}
state.time_backup = Some(*time);
let delta = if state.requested {
frame_step_duration(**frame_rate)
} else {
Duration::ZERO
};
time.advance_by(delta);
}
fn restore_frame_step_time(mut state: ResMut<FrameStepState>, mut time: ResMut<Time>) {
if let Some(backup) = state.time_backup.take() {
*time = backup;
state.requested = false;
}
}
fn activate_selected( fn activate_selected(
settings: &mut DebugSettings, settings: &mut DebugSettings,
page_len: usize, page_len: usize,
@@ -408,8 +490,10 @@ fn activate_selected(
} }
DebugMenuEntry::Toggle { flag, .. } => { DebugMenuEntry::Toggle { flag, .. } => {
settings.toggle_flag(flag); settings.toggle_flag(flag);
if !matches!(flag, DebugFlag::FrameStepping) {
queue_settings_save(commands); queue_settings_save(commands);
} }
}
DebugMenuEntry::Submenu { entries, .. } => { DebugMenuEntry::Submenu { entries, .. } => {
settings.push_page(DebugMenuPage::Static(entries)); settings.push_page(DebugMenuPage::Static(entries));
} }
@@ -623,9 +707,11 @@ fn draw_palette(
let mut value = settings.flag(*flag); let mut value = settings.flag(*flag);
if ui.checkbox(&mut value, *label).changed() { if ui.checkbox(&mut value, *label).changed() {
settings.toggle_flag(*flag); settings.toggle_flag(*flag);
if !matches!(flag, DebugFlag::FrameStepping) {
queue_settings_save(&mut commands); queue_settings_save(&mut commands);
} }
} }
}
DebugMenuEntry::Submenu { label, .. } DebugMenuEntry::Submenu { label, .. }
| DebugMenuEntry::PlayerSlots { label } => { | DebugMenuEntry::PlayerSlots { label } => {
if ui.button(format!("{label} >")).clicked() { if ui.button(format!("{label} >")).clicked() {
@@ -839,6 +925,15 @@ mod tests {
assert_eq!(GameSpeedDebug::Quarter.frame_rate(), 15); assert_eq!(GameSpeedDebug::Quarter.frame_rate(), 15);
} }
#[test]
fn frame_stepping_is_disabled_by_default_and_uses_game_speed_timestep() {
let settings = DebugSettings::default();
assert!(!settings.frame_stepping);
assert_eq!(frame_step_duration(60), Duration::from_nanos(16_666_666));
assert_eq!(frame_step_duration(30), Duration::from_nanos(33_333_333));
}
#[test] #[test]
fn empty_player_list_does_not_activate() { fn empty_player_list_does_not_activate() {
let mut settings = DebugSettings::default(); let mut settings = DebugSettings::default();