Compare commits

..
Author SHA1 Message Date
EIREXE 2e5680ac6a Persist Rust CI cache
CI / build-and-test (pull_request) Successful in 11m52s
2026-09-14 15:35:04 +02:00
EIREXE 504f63be54 Add support for split .gltf and .bin files (#3)
CI / build-and-test (push) Canceled after 15s
Reviewed-on: #3
Co-authored-by: Álex Román Núñez <eirexe@eirteam.moe>
2026-09-14 13:19:29 +00:00
4 changed files with 131 additions and 275 deletions
+7 -1
View File
@@ -20,7 +20,13 @@ jobs:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
sh -s -- -y --profile minimal sh -s -- -y --profile minimal
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
"$HOME/.cargo/bin/rustup" toolchain install nightly --profile minimal "$HOME/.cargo/bin/rustup" toolchain install nightly-2026-09-14 --profile minimal
- name: Cache Rust dependencies and build artifacts
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
shared-key: linux-nightly
cache-bin: false
- name: Install Linux dependencies - name: Install Linux dependencies
run: | run: |
+1 -1
View File
@@ -1,2 +1,2 @@
[toolchain] [toolchain]
channel = "nightly" channel = "nightly-2026-09-14"
+98 -246
View File
@@ -48,7 +48,7 @@ impl PresentationMotionMode {
} }
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumCycle, Reflect)] #[derive(EnumCycle, Reflect)]
pub enum GameSpeedDebug { pub enum GameSpeedDebug {
Full, Full,
Half, Half,
@@ -387,84 +387,6 @@ fn page_len(page: DebugMenuPage, player_handles: &[usize]) -> usize {
} }
} }
fn queue_settings_save(commands: &mut Commands) {
commands.queue(SaveSettingsDeferred(Duration::from_secs_f32(0.1)));
}
fn activate_selected(
settings: &mut DebugSettings,
page_len: usize,
player_handles: &[usize],
commands: &mut Commands,
) {
if page_len == 0 {
return;
}
match settings.page() {
DebugMenuPage::Static(entries) => match entries[settings.selected()] {
DebugMenuEntry::StopRecording { .. } => {
commands.insert_resource(StopReplayRecording);
}
DebugMenuEntry::Toggle { flag, .. } => {
settings.toggle_flag(flag);
queue_settings_save(commands);
}
DebugMenuEntry::Submenu { entries, .. } => {
settings.push_page(DebugMenuPage::Static(entries));
}
DebugMenuEntry::PlayerSlots { .. } => settings.push_page(DebugMenuPage::PlayerList),
DebugMenuEntry::MotionSampling { .. } => {
settings.cycle_motion_sampling();
queue_settings_save(commands);
}
DebugMenuEntry::GameSpeed { .. } => {
commands.insert_resource(RollbackFrameRate(settings.game_speed.frame_rate()));
queue_settings_save(commands);
}
},
DebugMenuPage::PlayerList => {
let handle = player_handles[settings.selected()];
settings.rows_mut(handle);
settings.push_page(DebugMenuPage::PlayerRows(handle));
}
DebugMenuPage::PlayerRows(_) => {}
}
}
fn cycle_selected_value(settings: &mut DebugSettings, direction: i8, commands: &mut Commands) {
match settings.page() {
DebugMenuPage::Static(entries) => {
if let Some(entry) = entries.get(settings.selected()) {
match entry {
DebugMenuEntry::MotionSampling { .. } => {
settings.cycle_motion_sampling();
queue_settings_save(commands);
}
DebugMenuEntry::GameSpeed { .. } => {
settings.game_speed = if direction > 0 {
settings.game_speed.up()
} else {
settings.game_speed.down()
};
commands
.insert_resource(RollbackFrameRate(settings.game_speed.frame_rate()));
queue_settings_save(commands);
}
_ => {}
}
}
}
DebugMenuPage::PlayerRows(handle) => {
let selected = settings.selected();
let rows = settings.rows_mut(handle);
rows[selected] = rows[selected].cycle(direction);
queue_settings_save(commands);
}
DebugMenuPage::PlayerList => {}
}
}
fn handle_palette_input( fn handle_palette_input(
keyboard: Res<ButtonInput<KeyCode>>, keyboard: Res<ButtonInput<KeyCode>>,
gamepads: Query<(Entity, &Gamepad)>, gamepads: Query<(Entity, &Gamepad)>,
@@ -476,15 +398,6 @@ fn handle_palette_input(
settings.toggle_flag(DebugFlag::DrawThroughGeometry); settings.toggle_flag(DebugFlag::DrawThroughGeometry);
} }
if keyboard.just_pressed(KeyCode::F3) {
settings.menu_open = !settings.menu_open;
settings.controller = None;
if settings.menu_open {
settings.reset_navigation();
}
return;
}
if !settings.menu_open { if !settings.menu_open {
for (entity, gamepad) in &gamepads { for (entity, gamepad) in &gamepads {
if gamepad.just_pressed(GamepadButton::Start) && gamepad.pressed(GamepadButton::DPadUp) if gamepad.just_pressed(GamepadButton::Start) && gamepad.pressed(GamepadButton::DPadUp)
@@ -498,16 +411,15 @@ fn handle_palette_input(
return; return;
} }
let gamepad = if let Some(controller) = settings.controller { let Some(controller) = settings.controller else {
settings.menu_open = false;
return;
};
let Ok((_, gamepad)) = gamepads.get(controller) else { let Ok((_, gamepad)) = gamepads.get(controller) else {
settings.menu_open = false; settings.menu_open = false;
settings.controller = None; settings.controller = None;
return; return;
}; };
Some(gamepad)
} else {
None
};
let player_handles = player_handles(&players); let player_handles = player_handles(&players);
let page = settings.page(); let page = settings.page();
let page_len = page_len(page, &player_handles); let page_len = page_len(page, &player_handles);
@@ -516,51 +428,95 @@ fn handle_palette_input(
*settings.selected_mut() = 0; *settings.selected_mut() = 0;
} }
if (gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::DPadUp)) if gamepad.just_pressed(GamepadButton::DPadUp) && page_len != 0 {
|| keyboard.just_pressed(KeyCode::ArrowUp)
|| keyboard.just_pressed(KeyCode::KeyW))
&& page_len != 0
{
let selected = settings.selected_mut(); let selected = settings.selected_mut();
*selected = selected.checked_sub(1).unwrap_or(page_len - 1); *selected = selected.checked_sub(1).unwrap_or(page_len - 1);
} }
if (gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::DPadDown)) if gamepad.just_pressed(GamepadButton::DPadDown) && page_len != 0 {
|| keyboard.just_pressed(KeyCode::ArrowDown)
|| keyboard.just_pressed(KeyCode::KeyS))
&& page_len != 0
{
let selected = settings.selected_mut(); let selected = settings.selected_mut();
*selected = (*selected + 1) % page_len; *selected = (*selected + 1) % page_len;
} }
let direction = if gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::DPadLeft)) let direction = if gamepad.just_pressed(GamepadButton::DPadLeft) {
|| keyboard.just_pressed(KeyCode::ArrowLeft)
|| keyboard.just_pressed(KeyCode::KeyA)
{
Some(-1) Some(-1)
} else if gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::DPadRight)) } else if gamepad.just_pressed(GamepadButton::DPadRight) {
|| keyboard.just_pressed(KeyCode::ArrowRight)
|| keyboard.just_pressed(KeyCode::KeyD)
{
Some(1) Some(1)
} else { } else {
None None
}; };
if let Some(direction) = direction { if let Some(direction) = direction {
cycle_selected_value(&mut settings, direction, &mut commands); match page {
DebugMenuPage::Static(entries) => {
if let Some(entry) = entries.get(settings.selected()) {
match entry {
DebugMenuEntry::StopRecording { .. } => {}
DebugMenuEntry::MotionSampling { .. } => settings.cycle_motion_sampling(),
DebugMenuEntry::GameSpeed { .. } => {
if direction > 0 {
settings.game_speed = settings.game_speed.up();
} else {
settings.game_speed = settings.game_speed.down();
} }
if (gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::South))
|| keyboard.just_pressed(KeyCode::Enter) commands.insert_resource(RollbackFrameRate(
|| keyboard.just_pressed(KeyCode::Space)) settings.game_speed.frame_rate(),
&& page_len != 0 ));
{
activate_selected(&mut settings, page_len, &player_handles, &mut commands);
} }
if (gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::West)) _ => {}
|| keyboard.just_pressed(KeyCode::Escape)) }
&& !settings.pop_page() }
{ }
DebugMenuPage::PlayerList => todo!(),
DebugMenuPage::PlayerRows(_) => {
let DebugMenuPage::PlayerRows(handle) = page else {
unreachable!("player row navigation must have a player row page")
};
let selected = settings.selected();
let rows = settings.rows_mut(handle);
rows[selected] = rows[selected].cycle(direction);
commands.queue(SaveSettingsDeferred(Duration::from_secs_f32(0.1)));
}
}
}
if matches!(page, DebugMenuPage::PlayerRows(_)) {
let direction = if gamepad.just_pressed(GamepadButton::DPadLeft) {
Some(-1)
} else if gamepad.just_pressed(GamepadButton::DPadRight) {
Some(1)
} else {
None
};
if let Some(_direction) = direction {}
}
if gamepad.just_pressed(GamepadButton::South) {
match page {
DebugMenuPage::Static(entries) if page_len != 0 => match entries[settings.selected()] {
DebugMenuEntry::StopRecording { .. } => {
commands.insert_resource(StopReplayRecording);
}
DebugMenuEntry::Toggle { flag, .. } => {
settings.toggle_flag(flag);
commands.queue(SaveSettingsDeferred(Duration::from_secs_f32(0.1)));
}
DebugMenuEntry::Submenu { entries, .. } => {
settings.push_page(DebugMenuPage::Static(entries));
}
DebugMenuEntry::PlayerSlots { .. } => settings.push_page(DebugMenuPage::PlayerList),
DebugMenuEntry::MotionSampling { .. } => settings.cycle_motion_sampling(),
DebugMenuEntry::GameSpeed { .. } => {}
},
DebugMenuPage::PlayerList if page_len != 0 => {
let handle = player_handles[settings.selected()];
settings.rows_mut(handle);
settings.push_page(DebugMenuPage::PlayerRows(handle));
}
_ => {}
}
}
if gamepad.just_pressed(GamepadButton::West) && !settings.pop_page() {
settings.menu_open = false; settings.menu_open = false;
settings.controller = None; settings.controller = None;
} }
@@ -582,9 +538,8 @@ fn sync_gizmo_depth_bias(settings: Res<DebugSettings>, mut configs: ResMut<Gizmo
fn draw_palette( fn draw_palette(
mut contexts: EguiContexts, mut contexts: EguiContexts,
mut settings: ResMut<DebugSettings>, settings: Res<DebugSettings>,
players: Query<&Player>, players: Query<&Player>,
mut commands: Commands,
) -> Result { ) -> Result {
if !settings.menu_open { if !settings.menu_open {
return Ok(()); return Ok(());
@@ -592,7 +547,7 @@ fn draw_palette(
let player_handles = player_handles(&players); let player_handles = player_handles(&players);
let page = settings.page(); let page = settings.page();
let page_len = page_len(page, &player_handles); let selected = settings.selected();
egui::Window::new("Debug") egui::Window::new("Debug")
.anchor(egui::Align2::RIGHT_TOP, egui::vec2(-12.0, 12.0)) .anchor(egui::Align2::RIGHT_TOP, egui::vec2(-12.0, 12.0))
.resizable(false) .resizable(false)
@@ -601,111 +556,38 @@ fn draw_palette(
match page { match page {
DebugMenuPage::Static(entries) => { DebugMenuPage::Static(entries) => {
for (index, entry) in entries.iter().enumerate() { for (index, entry) in entries.iter().enumerate() {
ui.horizontal(|ui| { let marker = if index == selected { ">" } else { " " };
ui.label(if index == settings.selected() {
">"
} else {
" "
});
match entry { match entry {
DebugMenuEntry::StopRecording { label } => { DebugMenuEntry::StopRecording { label } => {
if ui.button(*label).clicked() { ui.label(format!("{marker} {label}"));
*settings.selected_mut() = index;
activate_selected(
&mut settings,
page_len,
&player_handles,
&mut commands,
);
}
} }
DebugMenuEntry::Toggle { label, flag } => { DebugMenuEntry::Toggle { label, flag } => {
let mut value = settings.flag(*flag); let state = if settings.flag(*flag) { "on" } else { "off" };
if ui.checkbox(&mut value, *label).changed() { ui.label(format!("{marker} {label}: {state}"));
settings.toggle_flag(*flag);
queue_settings_save(&mut commands);
}
} }
DebugMenuEntry::Submenu { label, .. } DebugMenuEntry::Submenu { label, .. }
| DebugMenuEntry::PlayerSlots { label } => { | DebugMenuEntry::PlayerSlots { label } => {
if ui.button(format!("{label} >")).clicked() { ui.label(format!("{marker} {label} >"));
*settings.selected_mut() = index;
activate_selected(
&mut settings,
page_len,
&player_handles,
&mut commands,
);
}
} }
DebugMenuEntry::MotionSampling { label } => { DebugMenuEntry::MotionSampling { label } => {
let old = settings.motion_sampling; ui.label(format!(
egui::ComboBox::from_id_salt(("debug-motion", index)) "{marker} {label}: {}",
.width(180.0) settings.motion_sampling.label()
.selected_text(format!("{label}: {}", old.label())) ));
.show_ui(ui, |ui| {
ui.selectable_value(
&mut settings.motion_sampling,
PresentationMotionMode::Interpolation,
"Interpolation",
);
ui.selectable_value(
&mut settings.motion_sampling,
PresentationMotionMode::Extrapolation,
"Extrapolation",
);
});
if settings.motion_sampling != old {
queue_settings_save(&mut commands);
}
} }
DebugMenuEntry::GameSpeed { label } => { DebugMenuEntry::GameSpeed { label } => {
let old = settings.game_speed; ui.label(format!(
egui::ComboBox::from_id_salt(("debug-speed", index)) "{marker} {label}: {}",
.width(180.0) settings.game_speed.label()
.selected_text(format!("{label}: {}", old.label()))
.show_ui(ui, |ui| {
for speed in [
GameSpeedDebug::Full,
GameSpeedDebug::Half,
GameSpeedDebug::Quarter,
] {
ui.selectable_value(
&mut settings.game_speed,
speed,
speed.label(),
);
}
});
if settings.game_speed != old {
commands.insert_resource(RollbackFrameRate(
settings.game_speed.frame_rate(),
)); ));
queue_settings_save(&mut commands);
} }
} }
} }
});
}
} }
DebugMenuPage::PlayerList => { DebugMenuPage::PlayerList => {
for (index, handle) in player_handles.iter().enumerate() { for (index, handle) in player_handles.iter().enumerate() {
ui.horizontal(|ui| { let marker = if index == selected { ">" } else { " " };
ui.label(if index == settings.selected() { ui.label(format!("{marker} Player {} >", handle + 1));
">"
} else {
" "
});
if ui.button(format!("Player {} >", handle + 1)).clicked() {
*settings.selected_mut() = index;
activate_selected(
&mut settings,
page_len,
&player_handles,
&mut commands,
);
}
});
} }
if player_handles.is_empty() { if player_handles.is_empty() {
ui.label("No players in match"); ui.label("No players in match");
@@ -718,30 +600,16 @@ fn draw_palette(
.unwrap_or([FighterDebugRowKind::None; PLAYER_DEBUG_ROW_COUNT]); .unwrap_or([FighterDebugRowKind::None; PLAYER_DEBUG_ROW_COUNT]);
ui.label(format!("Player {}", handle + 1)); ui.label(format!("Player {}", handle + 1));
for (index, row) in rows.iter().enumerate() { for (index, row) in rows.iter().enumerate() {
ui.horizontal(|ui| { let marker = if index == selected { ">" } else { " " };
ui.label(if index == settings.selected() { ui.label(format!("{marker} Row {}: {}", index + 1, row.label()));
">"
} else {
" "
});
egui::ComboBox::from_id_salt(("debug-row", handle, index))
.width(180.0)
.selected_text(format!("Row {}: {}", index + 1, row.label()))
.show_ui(ui, |ui| {
let rows = settings.rows_mut(handle);
for kind in FighterDebugRowKind::ALL {
ui.selectable_value(&mut rows[index], kind, kind.label());
}
});
});
} }
} }
} }
ui.separator(); ui.separator();
let controls = if matches!(page, DebugMenuPage::PlayerRows(_)) { let controls = if matches!(page, DebugMenuPage::PlayerRows(_)) {
"Arrows/WASD: select/change Escape/B: back" "D-pad: select/change B: back"
} else { } else {
"Arrows/WASD: select Enter/A: activate Escape/B: back" "D-pad: select A: enter/toggle Left/right: change B: back"
}; };
ui.label(controls); ui.label(controls);
}); });
@@ -831,20 +699,4 @@ mod tests {
mode.cycle(); mode.cycle();
assert_eq!(mode, PresentationMotionMode::Interpolation); assert_eq!(mode, PresentationMotionMode::Interpolation);
} }
#[test]
fn game_speed_has_expected_frame_rates() {
assert_eq!(GameSpeedDebug::Full.frame_rate(), 60);
assert_eq!(GameSpeedDebug::Half.frame_rate(), 30);
assert_eq!(GameSpeedDebug::Quarter.frame_rate(), 15);
}
#[test]
fn empty_player_list_does_not_activate() {
let mut settings = DebugSettings::default();
settings.push_page(DebugMenuPage::PlayerList);
assert_eq!(page_len(settings.page(), &[]), 0);
assert_eq!(settings.selected(), 0);
}
} }
+8 -10
View File
@@ -3,6 +3,7 @@
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
io, io,
path::Path,
}; };
use bevy::{ use bevy::{
@@ -547,12 +548,8 @@ impl AssetLoader for FighterBakeSourceLoader {
let manifest: FighterManifest = ron::de::from_bytes(&manifest_bytes) let manifest: FighterManifest = ron::de::from_bytes(&manifest_bytes)
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?; .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
let model_path = load_context
.path()
.resolve_embed_str(&manifest.model_path)
.map_err(|error| io::Error::other(error.to_string()))?;
let model_bytes = load_context let model_bytes = load_context
.read_asset_bytes(model_path.clone_owned()) .read_asset_bytes(&manifest.model_path)
.await .await
.map_err(|error| io::Error::other(error.to_string()))?; .map_err(|error| io::Error::other(error.to_string()))?;
@@ -562,11 +559,12 @@ impl AssetLoader for FighterBakeSourceLoader {
for buffer in gltf.buffers() { for buffer in gltf.buffers() {
let bytes = match buffer.source() { let bytes = match buffer.source() {
gltf::buffer::Source::Uri(uri) => load_context gltf::buffer::Source::Uri(uri) => load_context
.read_asset_bytes( .read_asset_bytes(AssetPath::from_path_buf(
model_path Path::new(&manifest.model_path)
.resolve_embed_str(uri) .parent()
.map_err(|error| io::Error::other(error.to_string()))?, .unwrap_or_else(|| Path::new(""))
) .join(uri),
))
.await .await
.map_err(|error| io::Error::other(error.to_string()))?, .map_err(|error| io::Error::other(error.to_string()))?,
gltf::buffer::Source::Bin => gltf.blob.clone().ok_or_else(|| { gltf::buffer::Source::Bin => gltf.blob.clone().ok_or_else(|| {